Skip to content

Commit

Permalink
test: add logout admin e2e test
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentchalamon committed Apr 19, 2024
1 parent 0d7fcf7 commit 0d82561
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 15 deletions.
42 changes: 38 additions & 4 deletions pwa/components/admin/AppBar.tsx
@@ -1,13 +1,16 @@
import { useContext, useState } from "react";
import { AppBar, AppBarClasses, UserMenu, Logout, useStore } from "react-admin";
import { forwardRef, useContext, useState } from "react";
import { AppBar, AppBarClasses, LogoutClasses, UserMenu, useTranslate, useStore } from "react-admin";
import { type AppBarProps } from "react-admin";
import { Button, Menu, MenuItem, Typography } from "@mui/material";
import {Button, ListItemIcon, ListItemText, Menu, MenuItem, Typography} from "@mui/material";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import ExitIcon from "@mui/icons-material/PowerSettingsNew";
import { signOut, useSession } from "next-auth/react";

import DocContext from "../../components/admin/DocContext";
import HydraLogo from "../../components/admin/HydraLogo";
import OpenApiLogo from "../../components/admin/OpenApiLogo";
import Logo from "../../components/admin/Logo";
import {OIDC_SERVER_URL} from "../../config/keycloak";

const DocTypeMenuButton = () => {
const [anchorEl, setAnchorEl] = useState(null);
Expand Down Expand Up @@ -62,11 +65,42 @@ const DocTypeMenuButton = () => {
);
};

const Logout = forwardRef((props, ref) => {
const { data: session } = useSession();
const translate = useTranslate();

if (!session) {
return;
}

const handleClick = () => signOut({
// @ts-ignore
callbackUrl: `${OIDC_SERVER_URL}/protocol/openid-connect/logout?id_token_hint=${session.idToken}&post_logout_redirect_uri=${window.location.origin}`,
});

return (
<MenuItem
className="logout"
onClick={handleClick}
ref={ref}
component="li"
{...props}
>
<ListItemIcon className={LogoutClasses.icon}>
<ExitIcon fontSize="small" />
</ListItemIcon>
<ListItemText>
{translate('ra.auth.logout', { _: 'Logout' })}
</ListItemText>
</MenuItem>
);
});

const CustomAppBar = ({ ...props }: AppBarProps) => {
return (
<AppBar userMenu={
<UserMenu>
<Logout redirectTo={`${window.location.origin}/books`}/>
<Logout/>
</UserMenu>
} {...props}>
<Typography
Expand Down
15 changes: 7 additions & 8 deletions pwa/components/admin/authProvider.tsx
@@ -1,25 +1,24 @@
import { AuthProvider } from "react-admin";
import { signIn, signOut } from "next-auth/react";
import { signIn, signOut, useSession } from "next-auth/react";

import { auth } from "../../app/auth";
import { OIDC_SERVER_URL } from "../../config/keycloak";

const authProvider: AuthProvider = {
// Nothing to do here, this function will never be called
login: async () => Promise.resolve(),
logout: async () => {
const session = await auth();
const { data: session } = useSession();
if (!session) {
return;
}

await signOut(/*{
await signOut({
// @ts-ignore
callbackUrl: `${OIDC_SERVER_URL}/protocol/openid-connect/logout?id_token_hint=${session.idToken}&post_logout_redirect_uri=${window.location.origin}`,
}*/);
});
},
checkError: async (error) => {
const session = await auth();
const { data: session } = useSession();
const status = error.status;
// @ts-ignore
if (!session || session?.error === "RefreshAccessTokenError" || status === 401) {
Expand All @@ -33,7 +32,7 @@ const authProvider: AuthProvider = {
}
},
checkAuth: async () => {
const session = await auth();
const { data: session } = useSession();
// @ts-ignore
if (!session || session?.error === "RefreshAccessTokenError") {
await signIn("keycloak");
Expand All @@ -46,7 +45,7 @@ const authProvider: AuthProvider = {
getPermissions: () => Promise.resolve(),
// @ts-ignore
getIdentity: async () => {
const session = await auth();
const { data: session } = useSession();

return session ? Promise.resolve(session.user) : Promise.reject();
},
Expand Down
4 changes: 2 additions & 2 deletions pwa/tests/User.spec.ts
Expand Up @@ -5,7 +5,7 @@ test.describe("User authentication", () => {
await bookPage.gotoList();
});

test("I can log in @login", async ({ userPage, page }) => {
test("I can log in Books Store @login", async ({ userPage, page }) => {
await expect(page.getByText("Log in")).toBeVisible();
await expect(page.getByText("Sign out")).toHaveCount(0);

Expand All @@ -22,7 +22,7 @@ test.describe("User authentication", () => {
await expect(page.getByText("Sign out")).toBeVisible();
});

test("I can sign out @login", async ({ userPage, page }) => {
test("I can sign out of Books Store @login", async ({ userPage, page }) => {
await page.getByText("Log in").click();
await userPage.login();
await page.getByText("Sign out").click();
Expand Down
24 changes: 24 additions & 0 deletions pwa/tests/admin/User.spec.ts
@@ -0,0 +1,24 @@
import { expect, test } from "./test";

test.describe("User authentication", () => {
test.beforeEach(async ({ bookPage }) => {
await bookPage.gotoList();
});

test("I can sign out of Admin @login", async ({ userPage, page }) => {
await page.getByLabel("Profile").click();
await page.getByRole("menu").getByText("Logout").waitFor({ state: "visible" });
await page.getByRole("menu").getByText("Logout").click();

await expect(page).toHaveURL(/\/$/);

// I should be logged out from Keycloak also
await page.goto("/admin");
await page.waitForURL(/\/oidc\/realms\/demo\/protocol\/openid-connect\/auth/);
// @ts-ignore assert declared on test.ts
await expect(page).toBeOnLoginPage();
await expect(page.locator("#kc-header-wrapper")).toContainText("API Platform - Demo");
await expect(page.locator("#kc-form-login")).toContainText("Login as user: john.doe@example.com");
await expect(page.locator("#kc-form-login")).toContainText("Login as admin: chuck.norris@example.com");
});
});
4 changes: 4 additions & 0 deletions pwa/tests/admin/pages/UserPage.ts
@@ -0,0 +1,4 @@
import { AbstractPage } from "./AbstractPage";

export class UserPage extends AbstractPage {
}
23 changes: 22 additions & 1 deletion pwa/tests/admin/test.ts
@@ -1,12 +1,30 @@
import { test as playwrightTest } from "@playwright/test";
import { Page, test as playwrightTest } from "@playwright/test";

import { expect } from "../test";
import { BookPage } from "./pages/BookPage";
import { ReviewPage } from "./pages/ReviewPage";
import { UserPage } from "./pages/UserPage";

expect.extend({
toBeOnLoginPage(page: Page) {
if (page.url().match(/\/oidc\/realms\/demo\/protocol\/openid-connect\/auth/)) {
return {
message: () => "passed",
pass: true,
};
}

return {
message: () => `toBeOnLoginPage() assertion failed.\nExpected "/oidc/realms/demo/protocol/openid-connect/auth", got "${page.url()}".`,
pass: false,
};
},
});

type Test = {
bookPage: BookPage,
reviewPage: ReviewPage,
userPage: UserPage,
}

export const test = playwrightTest.extend<Test>({
Expand All @@ -16,6 +34,9 @@ export const test = playwrightTest.extend<Test>({
reviewPage: async ({ page }, use) => {
await use(new ReviewPage(page));
},
userPage: async ({ page }, use) => {
await use(new UserPage(page));
},
});

export { expect };

0 comments on commit 0d82561

Please sign in to comment.