Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

N21-2136-moin-schule-logout-in-svs #3444

Merged
merged 17 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 163 additions & 6 deletions src/modules/ui/layout/topbar/UserMenu.unit.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,38 @@
import { mount } from "@vue/test-utils";
import UserMenu from "./UserMenu.vue";
import {
createTestingI18n,
createTestingVuetify,
} from "@@/tests/test-utils/setup";
import { createModuleMocks } from "@@/tests/test-utils/mock-store-module";
import { envsFactory } from "@@/tests/test-utils";
import { AUTH_MODULE_KEY, ENV_CONFIG_MODULE_KEY } from "@/utils/inject";
import AuthModule from "@/store/auth";
import EnvConfigModule from "@/store/env-config";
import { LanguageType } from "@/serverApi/v3";
import { LanguageType, MeSystemResponse } from "@/serverApi/v3";
import { VBtn } from "vuetify/lib/components/index.mjs";
import UserMenu from "./UserMenu.vue";

jest.mock("@data-system");

describe("@ui-layout/UserMenu", () => {
const setup = () => {
const setupWrapper = (
isExternalFeatureEnabled = false,
mockedSystem?: MeSystemResponse
) => {
const authModule = createModuleMocks(AuthModule, {
getLocale: "de",
logout: jest.fn(),
externalLogout: jest.fn(),
get loginSystem(): MeSystemResponse | undefined {
return mockedSystem;
},
});

const envConfigModule = createModuleMocks(EnvConfigModule, {
getAvailableLanguages: [LanguageType.De, LanguageType.En],
getEnv: envsFactory.build({
FEATURE_EXTERNAL_SYSTEM_LOGOUT_ENABLED: isExternalFeatureEnabled,
}),
});

const wrapper = mount(UserMenu, {
Expand All @@ -41,15 +56,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");
Expand All @@ -61,7 +80,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");
Expand All @@ -72,4 +91,142 @@ describe("@ui-layout/UserMenu", () => {

expect(authModule.logout).toHaveBeenCalled();
});

describe("external logout", () => {
describe("when feature flag is enabled and end session endpoint is available for the system", () => {
const setup = () => {
const mockedSystem: MeSystemResponse = {
id: "testId",
name: "Test System",
hasEndSessionEndpoint: true,
};

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(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.name}`
);
});

it("should trigger external logout function on logout item click", async () => {
const { wrapper, authModule } = setup();

const menuBtn = wrapper.findComponent(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(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: MeSystemResponse = {
id: "testId",
name: "Test System",
hasEndSessionEndpoint: true,
};

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(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 end session endpoint is not available for the systeme", () => {
const setup = () => {
const mockedSystem: MeSystemResponse = {
id: "testId",
name: "Test System",
hasEndSessionEndpoint: false,
};

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(VBtn);
await menuBtn.trigger("click");

const logoutBtn = wrapper.findComponent("[data-testid=logout]");

expect(logoutBtn.exists()).toBe(true);
expect(logoutBtn.text()).toEqual("common.labels.logout");
});
});
});
});
34 changes: 30 additions & 4 deletions src/modules/ui/layout/topbar/UserMenu.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<VMenu width="215">
<VMenu :width="isExternalLogoutAllowed ? 'auto' : '215'">
<template v-slot:activator="{ props }">
<VBtn
v-bind="props"
Expand All @@ -21,19 +21,32 @@
<VListItem href="/account" data-testid="account-link">
{{ $t("global.topbar.settings") }}
</VListItem>
<VListItem
v-if="isExternalLogoutAllowed"
data-testid="external-logout"
@click="externalLogout"
>
{{ $t("common.labels.logout")
}}{{ isExternalLogoutAllowed ? ` Bildungscloud & ${systemName}` : "" }}
</VListItem>
<VListItem data-testid="logout" @click="logout">
{{ $t("common.labels.logout") }}
{{ $t("common.labels.logout")
}}{{ isExternalLogoutAllowed ? " Bildungscloud" : "" }}
</VListItem>
</VList>
</VMenu>
</template>

<script setup lang="ts">
import { computed, PropType, toRef } from "vue";
import { computed, ComputedRef, PropType, toRef } from "vue";
import { useI18n } from "vue-i18n";
import LanguageMenu from "./LanguageMenu.vue";
import { MeUserResponse } from "@/serverApi/v3";
import { injectStrict, AUTH_MODULE_KEY } from "@/utils/inject";
import {
injectStrict,
AUTH_MODULE_KEY,
ENV_CONFIG_MODULE_KEY,
} from "@/utils/inject";

const props = defineProps({
user: {
Expand All @@ -48,6 +61,7 @@ const props = defineProps({

const { t } = useI18n();
const authModule = injectStrict(AUTH_MODULE_KEY);
const envConfigModule = injectStrict(ENV_CONFIG_MODULE_KEY);

const userRole = computed(() => {
return t(`common.roleName.${toRef(props.roleNames).value[0]}`).toString();
Expand All @@ -57,9 +71,21 @@ const initials = computed(() => {
return props.user.firstName.slice(0, 1) + props.user.lastName.slice(0, 1);
});

const isExternalLogoutAllowed: ComputedRef<boolean> = computed(
() =>
envConfigModule.getEnv.FEATURE_EXTERNAL_SYSTEM_LOGOUT_ENABLED &&
!!authModule.loginSystem?.hasEndSessionEndpoint
);

const systemName: string = authModule.loginSystem?.name ?? "System";

const logout = () => {
authModule.logout();
};

const externalLogout = () => {
authModule.externalLogout();
};
</script>

<style scoped>
Expand Down
Loading
Loading