From 85bbdbf109dca7f0ec0aadf4456fb6c36c69b464 Mon Sep 17 00:00:00 2001 From: Gordon Nicholas Date: Fri, 8 Nov 2024 09:35:37 +0100 Subject: [PATCH 1/9] N21-2166 external logout buttons --- .../data/system/SystemApi.composable.ts | 1 + src/modules/data/system/type/System.ts | 2 + src/modules/ui/layout/topbar/UserMenu.unit.ts | 183 ++++++- src/modules/ui/layout/topbar/UserMenu.vue | 64 ++- src/serverApi/v3/api.ts | 453 ++++++++++++++++++ src/store/auth.ts | 5 + src/store/env-config-defaults.ts | 1 + 7 files changed, 700 insertions(+), 9 deletions(-) diff --git a/src/modules/data/system/SystemApi.composable.ts b/src/modules/data/system/SystemApi.composable.ts index b67365e102..ffe036911d 100644 --- a/src/modules/data/system/SystemApi.composable.ts +++ b/src/modules/data/system/SystemApi.composable.ts @@ -15,6 +15,7 @@ export const useSystemApi = () => { const system: System = { id: response.data.id, + alias: response.data.alias, displayName: response.data.displayName ?? "", }; diff --git a/src/modules/data/system/type/System.ts b/src/modules/data/system/type/System.ts index 4db6241e1d..b0164c9a32 100644 --- a/src/modules/data/system/type/System.ts +++ b/src/modules/data/system/type/System.ts @@ -1,5 +1,7 @@ export interface System { id: string; + alias?: string | null; + displayName: string; } diff --git a/src/modules/ui/layout/topbar/UserMenu.unit.ts b/src/modules/ui/layout/topbar/UserMenu.unit.ts index 25a93766ad..05302762a1 100644 --- a/src/modules/ui/layout/topbar/UserMenu.unit.ts +++ b/src/modules/ui/layout/topbar/UserMenu.unit.ts @@ -5,27 +5,58 @@ import { createTestingVuetify, } from "@@/tests/test-utils/setup"; import { createModuleMocks } from "@@/tests/test-utils/mock-store-module"; -import { AUTH_MODULE_KEY, ENV_CONFIG_MODULE_KEY } from "@/utils/inject"; +import { envsFactory } from "@@/tests/test-utils"; +import { + AUTH_MODULE_KEY, + ENV_CONFIG_MODULE_KEY, + NOTIFIER_MODULE_KEY, +} from "@/utils/inject"; import AuthModule from "@/store/auth"; import EnvConfigModule from "@/store/env-config"; import { LanguageType } from "@/serverApi/v3"; +import { System, useSystemApi } from "@data-system"; +import { createMock, DeepMocked } from "@golevelup/ts-jest"; +import NotifierModule from "@/store/notifier"; +import { VBtn } from "vuetify/lib/components/index.mjs"; + +jest.mock("@data-system"); describe("@ui-layout/UserMenu", () => { - const setup = () => { + let useSystemApiMock: DeepMocked>; + + const setupWrapper = ( + isExternalFeatureEnabled = false, + mockedSystem?: System + ) => { const authModule = createModuleMocks(AuthModule, { getLocale: "de", logout: jest.fn(), + externalLogout: jest.fn(), + getAccessToken: + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic3lzdGVtSWQiOiJhYmMxMjMiLCJpYX" + + "QiOjE1MTYyMzkwMjJ9.Zrpvm5T50Y_s-pd5OoqdxKRBaf3rJGMAviUz7Be84zA", }); + const envConfigModule = createModuleMocks(EnvConfigModule, { getAvailableLanguages: [LanguageType.De, LanguageType.En], + getEnv: envsFactory.build({ + FEATURE_EXTERNAL_SYSTEM_LOGOUT_ENABLED: isExternalFeatureEnabled, + }), }); + const notifierModule = createModuleMocks(NotifierModule); + + useSystemApiMock = createMock>(); + jest.mocked(useSystemApi).mockReturnValue(useSystemApiMock); + useSystemApiMock.getSystem.mockResolvedValue(mockedSystem); + const wrapper = mount(UserMenu, { global: { plugins: [createTestingVuetify(), createTestingI18n()], provide: { [AUTH_MODULE_KEY.valueOf()]: authModule, [ENV_CONFIG_MODULE_KEY.valueOf()]: envConfigModule, + [NOTIFIER_MODULE_KEY.valueOf()]: notifierModule, }, }, props: { @@ -41,15 +72,19 @@ describe("@ui-layout/UserMenu", () => { return { wrapper, authModule }; }; + afterEach(() => { + jest.clearAllMocks(); + }); + it("should render with correct user initials", async () => { - const { wrapper } = setup(); + const { wrapper } = setupWrapper(); const initials = wrapper.findComponent("[data-testid=user-menu-btn]"); expect(initials.text()).toMatch("AD"); }); it("should render correct active user name with role", async () => { - const { wrapper } = setup(); + const { wrapper } = setupWrapper(); const menuBtn = wrapper.findComponent({ name: "VBtn" }); await menuBtn.trigger("click"); @@ -61,7 +96,7 @@ describe("@ui-layout/UserMenu", () => { }); it("should trigger logout function on logout item click", async () => { - const { wrapper, authModule } = setup(); + const { wrapper, authModule } = setupWrapper(); const menuBtn = wrapper.findComponent({ name: "VBtn" }); await menuBtn.trigger("click"); @@ -72,4 +107,142 @@ describe("@ui-layout/UserMenu", () => { expect(authModule.logout).toHaveBeenCalled(); }); + + describe("external logout", () => { + describe("when feature flag is enabled and the user is logged in from moin.schule", () => { + const setup = () => { + const mockedSystem: System = { + id: "testId", + displayName: "moin.schule", + alias: "SANIS", + }; + + const { wrapper, authModule } = setupWrapper(true, mockedSystem); + + return { wrapper, authModule, mockedSystem }; + }; + + it("should show the external logout button", async () => { + const { wrapper, mockedSystem } = setup(); + + const menuBtn = wrapper.findComponent({ name: "VBtn" }); + await menuBtn.trigger("click"); + + const externalLogoutBtn = wrapper.findComponent( + "[data-testid=external-logout]" + ); + + expect(externalLogoutBtn.exists()).toBe(true); + expect(externalLogoutBtn.text()).toEqual( + `common.labels.logout Bildungscloud & ${mockedSystem.displayName}` + ); + }); + + it("should trigger external logout function on logout item click", async () => { + const { wrapper, authModule } = setup(); + + const menuBtn = wrapper.findComponent({ name: "VBtn" }); + await menuBtn.trigger("click"); + + const externalLogoutBtn = wrapper.findComponent( + "[data-testid=external-logout]" + ); + + expect(externalLogoutBtn.exists()).toBe(true); + await externalLogoutBtn.trigger("click"); + + expect(authModule.externalLogout).toHaveBeenCalled(); + }); + + it("should show the correct text for the logout button", async () => { + const { wrapper } = setup(); + + const menuBtn = wrapper.findComponent({ name: "VBtn" }); + await menuBtn.trigger("click"); + + const logoutBtn = wrapper.findComponent("[data-testid=logout]"); + + expect(logoutBtn.exists()).toBe(true); + expect(logoutBtn.text()).toEqual(`common.labels.logout Bildungscloud`); + }); + }); + + describe("when feature flag is disabled", () => { + const setup = () => { + const mockedSystem: System = { + id: "testId", + displayName: "moin.schule", + alias: "SANIS", + }; + + const { wrapper } = setupWrapper(false, mockedSystem); + + return { wrapper }; + }; + + it("should not show the external logout button", async () => { + const { wrapper } = setup(); + + const menuBtn = wrapper.findComponent(VBtn); + await menuBtn.trigger("click"); + + const externalLogoutBtn = wrapper.findComponent( + "[data-testid=external-logout]" + ); + + expect(externalLogoutBtn.exists()).toBe(false); + }); + + it("should show the correct text for the logout button", async () => { + const { wrapper } = setup(); + + const menuBtn = wrapper.findComponent({ name: "VBtn" }); + await menuBtn.trigger("click"); + + const logoutBtn = wrapper.findComponent("[data-testid=logout]"); + + expect(logoutBtn.exists()).toBe(true); + expect(logoutBtn.text()).toEqual("common.labels.logout"); + }); + }); + + describe("when user is not logged in from moin.schule", () => { + const setup = () => { + const mockedSystem: System = { + id: "testId", + displayName: "mock", + alias: "mock-system", + }; + + const { wrapper } = setupWrapper(true, mockedSystem); + + return { wrapper }; + }; + + it("should not show the external logout button", async () => { + const { wrapper } = setup(); + + const menuBtn = wrapper.findComponent(VBtn); + await menuBtn.trigger("click"); + + const externalLogoutBtn = wrapper.findComponent( + "[data-testid=external-logout]" + ); + + expect(externalLogoutBtn.exists()).toBe(false); + }); + + it("should show the correct text for the logout button", async () => { + const { wrapper } = setup(); + + const menuBtn = wrapper.findComponent({ name: "VBtn" }); + await menuBtn.trigger("click"); + + const logoutBtn = wrapper.findComponent("[data-testid=logout]"); + + expect(logoutBtn.exists()).toBe(true); + expect(logoutBtn.text()).toEqual("common.labels.logout"); + }); + }); + }); }); diff --git a/src/modules/ui/layout/topbar/UserMenu.vue b/src/modules/ui/layout/topbar/UserMenu.vue index 40a7cf39e0..83a581054e 100644 --- a/src/modules/ui/layout/topbar/UserMenu.vue +++ b/src/modules/ui/layout/topbar/UserMenu.vue @@ -1,5 +1,5 @@