-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Aurélien Bompard <aurelien@bompard.org>
- Loading branch information
Showing
5 changed files
with
244 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// SPDX-FileCopyrightText: Contributors to the Fedora Project | ||
// | ||
// SPDX-License-Identifier: MIT | ||
|
||
import axios, { type AxiosInstance } from "axios"; | ||
import { vi } from "vitest"; | ||
|
||
export const apiClient = { | ||
defaults: axios.defaults, | ||
get: vi.fn(), | ||
} as unknown as AxiosInstance; | ||
|
||
export async function getApiClient() { | ||
return apiClient; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
// SPDX-FileCopyrightText: Contributors to the Fedora Project | ||
// | ||
// SPDX-License-Identifier: MIT | ||
|
||
import { apiClient } from "@/api/__mocks__"; | ||
import { useToastStore } from "@/stores/toast"; | ||
import { useUserStore } from "@/stores/user"; | ||
import { getRenderOptions } from "@/util/tests"; | ||
import { TokenResponse } from "@openid/appauth"; | ||
import { | ||
cleanup, | ||
render, | ||
waitFor, | ||
type RenderOptions, | ||
} from "@testing-library/vue"; | ||
import { createPinia, setActivePinia } from "pinia"; | ||
import { | ||
afterEach, | ||
beforeEach, | ||
describe, | ||
expect, | ||
it, | ||
vi, | ||
type Mocked, | ||
} from "vitest"; | ||
import router from "../router"; | ||
import LoginFedora from "./LoginFedora.vue"; | ||
import type Authenticator from "./authenticator"; | ||
import type { AuthorizationRedirectListener } from "./authenticator"; | ||
|
||
vi.mock("@/api"); | ||
|
||
describe("LoginFedora", () => { | ||
let tokenResponse: TokenResponse; | ||
let renderOptions: RenderOptions; | ||
let auth: Mocked<Authenticator>; | ||
|
||
beforeEach(async () => { | ||
await router.replace("/login/fedora"); | ||
await router.isReady(); | ||
setActivePinia(createPinia()); | ||
renderOptions = getRenderOptions(); | ||
auth = renderOptions.global?.provide?.auth; | ||
auth.handleAuthorizationRedirect.mockImplementation( | ||
async (listener: AuthorizationRedirectListener) => { | ||
listener(tokenResponse); | ||
} | ||
); | ||
}); | ||
afterEach(() => { | ||
cleanup(); | ||
vi.restoreAllMocks(); | ||
sessionStorage.clear(); | ||
}); | ||
|
||
it("renders", () => { | ||
const { getByText } = render(LoginFedora, renderOptions); | ||
expect(getByText("Loading user information...")).toBeInTheDocument(); | ||
}); | ||
|
||
it("redirects if already logged in", async () => { | ||
const store = useUserStore(); | ||
store.$patch({ | ||
accessToken: "testing", | ||
username: "dummy-user", | ||
fullName: "Dummy User", | ||
email: "dummy@example.com", | ||
}); | ||
render(LoginFedora, renderOptions); | ||
await waitFor(() => { | ||
expect(router.currentRoute.value.path).toBe("/"); | ||
}); | ||
}); | ||
|
||
it("handles the incoming OIDC data", async () => { | ||
tokenResponse = new TokenResponse({ | ||
access_token: "dummy-access-token", | ||
refresh_token: "dummy-refresh-token", | ||
scope: "dummy-serverside-scope", | ||
}); | ||
const userInfoResponse = { | ||
sub: "dummy-sub", | ||
nickname: "dummy-username", | ||
}; | ||
auth.makeUserInfoRequest.mockResolvedValue(userInfoResponse); | ||
vi.mocked(apiClient.get).mockResolvedValue({ data: { is_admin: false } }); | ||
|
||
render(LoginFedora, renderOptions); | ||
|
||
const store = useUserStore(); | ||
|
||
await waitFor(() => { | ||
expect(store.loggedIn).toBe(true); | ||
// The call to the API is async | ||
expect(vi.mocked(apiClient.get)).toHaveBeenCalled(); | ||
}); | ||
|
||
expect(auth.makeUserInfoRequest).toHaveBeenCalledWith("dummy-access-token"); | ||
expect(vi.mocked(apiClient.get)).toHaveBeenCalledWith("/api/v1/users/me"); | ||
expect(store.$state).toStrictEqual({ | ||
accessToken: "dummy-access-token", | ||
refreshToken: "dummy-refresh-token", | ||
idToken: null, | ||
tokenExpiresAt: null, | ||
scopes: [], | ||
username: "dummy-username", | ||
fullName: "dummy-username", | ||
email: "", | ||
isAdmin: false, | ||
}); | ||
// We must have been redirected | ||
await waitFor(() => { | ||
expect(router.currentRoute.value.path).toBe("/"); | ||
}); | ||
const toastStore = useToastStore(); | ||
expect(toastStore.toasts).toHaveLength(1); | ||
expect(toastStore.toasts[0].title).toBe("Login successful!"); | ||
expect(toastStore.toasts[0].content).toBe("Welcome, dummy-username."); | ||
expect(toastStore.toasts[0].color).toBe("success"); | ||
}); | ||
|
||
it("handles the incoming OIDC data about an admin", async () => { | ||
tokenResponse = new TokenResponse({ | ||
access_token: "dummy-access-token", | ||
refresh_token: "dummy-refresh-token", | ||
scope: "dummy-serverside-scope", | ||
}); | ||
auth.makeUserInfoRequest.mockResolvedValue({ | ||
sub: "dummy-sub", | ||
nickname: "dummy-username", | ||
}); | ||
vi.mocked(apiClient.get).mockResolvedValue({ data: { is_admin: true } }); | ||
|
||
render(LoginFedora, renderOptions); | ||
|
||
const store = useUserStore(); | ||
|
||
await waitFor(() => { | ||
expect(vi.mocked(apiClient.get)).toHaveBeenCalled(); | ||
}); | ||
expect(store.loggedIn).toBe(true); | ||
expect(store.isAdmin).toBe(true); | ||
}); | ||
|
||
it("displays authentication errors", async () => { | ||
const { getByText } = render(LoginFedora, renderOptions); | ||
auth.handleAuthorizationRedirect.mockRejectedValue("Dummy Error"); | ||
await waitFor(() => { | ||
expect(getByText("Dummy Error")).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
it("redirects to the right place on login", async () => { | ||
tokenResponse = new TokenResponse({ | ||
access_token: "dummy-access-token", | ||
}); | ||
auth.makeUserInfoRequest.mockResolvedValue({ | ||
sub: "dummy-sub", | ||
nickname: "dummy-username", | ||
}); | ||
vi.mocked(apiClient.get).mockResolvedValue({ data: { is_admin: false } }); | ||
sessionStorage.setItem("redirect_to", "/dummy/page"); | ||
|
||
render(LoginFedora, renderOptions); | ||
|
||
await waitFor(() => { | ||
expect(router.currentRoute.value.path).toBe("/dummy/page"); | ||
}); | ||
expect(sessionStorage.getItem("redirect_to")).toBeNull(); | ||
}); | ||
|
||
it("displays authentication errors", async () => { | ||
const { getByText } = render(LoginFedora, renderOptions); | ||
auth.handleAuthorizationRedirect.mockRejectedValue("Dummy Error"); | ||
await waitFor(() => { | ||
expect(getByText("Dummy Error")).toBeInTheDocument(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// SPDX-FileCopyrightText: Contributors to the Fedora Project | ||
// | ||
// SPDX-License-Identifier: MIT | ||
|
||
import { vueQueryPluginOptions } from "@/api"; | ||
import type { UserInfoResponseJson } from "@/auth/userinfo_request"; | ||
import { config as formkitConfig } from "@/forms/index"; | ||
import { plugin as FormKitPlugin } from "@formkit/vue"; | ||
import type { RenderOptions } from "@testing-library/vue"; | ||
import { getActivePinia, type Pinia } from "pinia"; | ||
import { vi } from "vitest"; | ||
import { createI18n } from "vue-i18n"; | ||
import { VueQueryPlugin } from "vue-query"; | ||
import router from "../router"; | ||
|
||
export const getRenderOptions = (): RenderOptions => { | ||
const pinia = getActivePinia() as Pinia; | ||
const i18n = createI18n({ | ||
legacy: false, | ||
locale: navigator.language, | ||
fallbackLocale: "en-US", | ||
messages: {}, | ||
}); | ||
return { | ||
global: { | ||
plugins: [ | ||
router, | ||
pinia, | ||
i18n, | ||
[FormKitPlugin, formkitConfig], | ||
[VueQueryPlugin, vueQueryPluginOptions], | ||
], | ||
provide: { | ||
auth: { | ||
fetchServiceConfiguration: vi.fn().mockResolvedValue(null), | ||
handleAuthorizationRedirect: vi.fn().mockResolvedValue(null), | ||
makeUserInfoRequest: vi | ||
.fn() | ||
.mockResolvedValue({} as UserInfoResponseJson), | ||
}, | ||
}, | ||
}, | ||
}; | ||
}; |