Skip to content
This repository was archived by the owner on Feb 28, 2025. It is now read-only.

Commit d5c20c2

Browse files
adjust sonar, add route guard tests
1 parent 25d5be6 commit d5c20c2

10 files changed

+237
-17
lines changed

jest.config.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ const config = deepmerge(defaultPreset, {
2626

2727
collectCoverageFrom: [
2828
// Include
29-
"<rootDir>/src/layouts/**/*.{js,ts,vue}",
30-
"<rootDir>/src/modules/**/*.{js,ts,vue}",
29+
"<rootDir>/src/layouts/**/*.{js,ts}", // add vue files
30+
"<rootDir>/src/modules/**/*.{js,ts}", // add vue files
3131
"<rootDir>/src/plugins/**/*.(js|ts)",
32-
// "<rootDir>/src/router/guards/**/*.(js|ts)",
32+
"<rootDir>/src/router/guards/**/*.(js|ts)",
3333
"<rootDir>/src/utils/**/*.(js|ts)",
3434

3535
// Exclude

sonar-project.properties

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ sonar.organization=schulcloud-verbund
22
sonar.projectKey=hpi-schul-cloud_shd-client
33

44
sonar.sources=.
5-
sonar.tests=.
5+
sonar.tests=src/
66

77
# Exclude test files, locales and generated code from source scope
8-
sonar.exclusions=tests/**/*,**/*.unit.ts,**/*.unit.js,src/serverApi/**/*,**/locales/**.ts
8+
sonar.exclusions=src/serverApi/**/*,**/locales/**.ts
99

10-
# Include test files in test scope
10+
# Include test files and test utils in test scope
1111
sonar.test.inclusions=tests/**/*,**/*.unit.ts,**/*.unit.js
1212

1313
# Coverage report directory of jest

src/modules/page/AboutView.unit.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
1+
import { createTestingVuetify } from "@@/tests/test-utils/setup";
12
import { mount } from "@vue/test-utils";
3+
import { VBtn } from "vuetify/lib/components/index.mjs";
24
import AboutView from "./AboutView.vue";
35

46
describe("AboutView", () => {
57
const getWrapper = () => {
6-
const wrapper = mount(AboutView);
8+
const wrapper = mount(AboutView, {
9+
global: { plugins: [createTestingVuetify()] },
10+
});
711

812
return {
913
wrapper,
1014
};
1115
};
1216

13-
it("should improve the code coverage", () => {
17+
it("should increase the counter button on click", async () => {
1418
const { wrapper } = getWrapper();
1519

16-
expect(wrapper).toBeDefined();
20+
const counter = wrapper.getComponent(VBtn);
21+
await counter.trigger("click");
22+
23+
expect(counter.text()).toEqual("1");
1724
});
1825
});

src/modules/page/AboutView.vue

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
<template>
22
<main>
33
<h3>This is the new and improved Superhero-Dashboard!</h3>
4+
<VBtn @click="counter++">{{ counter }}</VBtn>
45
</main>
56
</template>
67

7-
<script setup lang="ts"></script>
8+
<script setup lang="ts">
9+
import { Ref, ref } from "vue";
10+
11+
const counter: Ref<number> = ref(0);
12+
</script>

src/router/guards/is-authenticated.guard.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useAuthStore } from "@data/auth";
22
import { NavigationGuardNext, RouteLocationNormalized } from "vue-router";
3-
import { getLoginUrlWithRedirect } from "../login-redirect-url";
3+
import { getLoginUrlWithRedirect } from "./login-redirect-url";
44

55
export const isAuthenticatedGuard = (
66
to: RouteLocationNormalized,
+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { meResponseFactory } from "@@/tests/test-utils/factory";
2+
import { useAuthStore } from "@data/auth";
3+
import { createTestingPinia } from "@pinia/testing";
4+
import { setActivePinia } from "pinia";
5+
import { RouteLocationNormalized } from "vue-router";
6+
import { isAuthenticatedGuard } from "./is-authenticated.guard";
7+
8+
jest.mock("./login-redirect-url", () => ({
9+
getLoginUrlWithRedirect: () => "login-url",
10+
}));
11+
12+
describe("Authentication Guard", () => {
13+
beforeEach(() => {
14+
setActivePinia(createTestingPinia());
15+
});
16+
17+
afterEach(() => {
18+
jest.clearAllMocks();
19+
});
20+
21+
describe("isAuthenticatedGuard", () => {
22+
describe("when authenticated", () => {
23+
const setup = () => {
24+
const to: RouteLocationNormalized = {
25+
fullPath: "/test",
26+
} as RouteLocationNormalized;
27+
const from: RouteLocationNormalized = {} as RouteLocationNormalized;
28+
const next = jest.fn();
29+
const authStore = useAuthStore();
30+
31+
authStore.me = meResponseFactory.build();
32+
33+
return {
34+
to,
35+
from,
36+
next,
37+
};
38+
};
39+
40+
it("should pass", () => {
41+
const { to, from, next } = setup();
42+
43+
isAuthenticatedGuard(to, from, next);
44+
45+
expect(next).toHaveBeenCalled();
46+
});
47+
});
48+
49+
describe("when the url is public", () => {
50+
const setup = () => {
51+
const to: RouteLocationNormalized = {
52+
fullPath: "/test",
53+
meta: {
54+
isPublic: true,
55+
},
56+
} as unknown as RouteLocationNormalized;
57+
const from: RouteLocationNormalized = {} as RouteLocationNormalized;
58+
const next = jest.fn();
59+
const authStore = useAuthStore();
60+
61+
authStore.me = null;
62+
63+
return {
64+
to,
65+
from,
66+
next,
67+
};
68+
};
69+
70+
it("should pass", () => {
71+
const { to, from, next } = setup();
72+
73+
isAuthenticatedGuard(to, from, next);
74+
75+
expect(next).toHaveBeenCalled();
76+
});
77+
});
78+
79+
describe("when not authenticated and the url is not public", () => {
80+
const setup = () => {
81+
const to: RouteLocationNormalized = {
82+
fullPath: "/test",
83+
} as RouteLocationNormalized;
84+
const from: RouteLocationNormalized = {} as RouteLocationNormalized;
85+
const next = jest.fn();
86+
const authStore = useAuthStore();
87+
88+
authStore.me = null;
89+
90+
const assign = jest.fn();
91+
Object.defineProperty(window, "location", {
92+
configurable: true,
93+
value: { assign },
94+
});
95+
96+
return {
97+
to,
98+
from,
99+
next,
100+
assign,
101+
};
102+
};
103+
104+
it("should redirect to login", () => {
105+
const { to, from, next, assign } = setup();
106+
107+
isAuthenticatedGuard(to, from, next);
108+
109+
expect(assign).toHaveBeenCalledWith("login-url");
110+
});
111+
112+
it("should not pass", () => {
113+
const { to, from, next } = setup();
114+
115+
isAuthenticatedGuard(to, from, next);
116+
117+
expect(next).not.toHaveBeenCalled();
118+
});
119+
});
120+
});
121+
});

src/router/login-redirect-url.ts renamed to src/router/guards/login-redirect-url.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useEnvConfigStore } from "@data/env-config";
22

3-
export const getLoginUrlWithRedirect = (targetPath: string) => {
3+
export const getLoginUrlWithRedirect = (targetPath: string): string => {
44
const currentOrigin = window.location.origin;
55

66
const currentUrl = new URL(targetPath, currentOrigin);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { envsFactory } from "@@/tests/test-utils/factory";
2+
import { useEnvConfigStore } from "@data/env-config";
3+
import { createPinia, setActivePinia } from "pinia";
4+
import { getLoginUrlWithRedirect } from "./login-redirect-url";
5+
6+
describe("Login Redirect Util", () => {
7+
beforeEach(() => {
8+
setActivePinia(createPinia());
9+
});
10+
11+
afterEach(() => {
12+
jest.clearAllMocks();
13+
});
14+
15+
describe("getLoginUrlWithRedirect", () => {
16+
describe("when redirecting to internal login before redirecting to the target url", () => {
17+
const setup = () => {
18+
const loginUrl = "https://test.com/login";
19+
const targetPath = "/dashboard";
20+
const origin = "https://test.com";
21+
const envConfigStore = useEnvConfigStore();
22+
23+
envConfigStore.setEnvs(
24+
envsFactory.build({
25+
NOT_AUTHENTICATED_REDIRECT_URL: loginUrl,
26+
})
27+
);
28+
29+
jest.spyOn(window, "location", "get").mockReturnValue({
30+
origin: origin,
31+
} as Location);
32+
33+
return {
34+
loginUrl,
35+
targetPath,
36+
origin,
37+
};
38+
};
39+
40+
it("should redirect to internal login with post-login-redirect to internal target url", () => {
41+
const { loginUrl, targetPath, origin } = setup();
42+
43+
const result = getLoginUrlWithRedirect(targetPath);
44+
45+
expect(result).toEqual(
46+
`${loginUrl}?redirect=${encodeURIComponent(`${origin}${targetPath}`)}`
47+
);
48+
});
49+
});
50+
51+
describe("when redirecting to an external login before redirecting to the target url", () => {
52+
const setup = () => {
53+
const origin = "https://test.com";
54+
const loginUrl = `https://external-login.thr/login?service=${encodeURIComponent(origin)}`;
55+
const targetPath = "/dashboard";
56+
const envConfigStore = useEnvConfigStore();
57+
58+
envConfigStore.setEnvs(
59+
envsFactory.build({
60+
NOT_AUTHENTICATED_REDIRECT_URL: loginUrl,
61+
})
62+
);
63+
64+
jest.spyOn(window, "location", "get").mockReturnValue({
65+
origin: origin,
66+
} as Location);
67+
68+
return {
69+
loginUrl,
70+
targetPath,
71+
origin,
72+
};
73+
};
74+
75+
it("should redirect to external login with post-login-redirect to internal target url", () => {
76+
const { loginUrl, targetPath, origin } = setup();
77+
78+
const result = getLoginUrlWithRedirect(targetPath);
79+
80+
const redirectUri = encodeURIComponent(`${origin}${targetPath}`);
81+
expect(result).toEqual(
82+
`${loginUrl}${encodeURIComponent(`/?redirect=${redirectUri}`)}`
83+
);
84+
});
85+
});
86+
});
87+
});

src/utils/api/api.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ export const mapAxiosErrorToResponseError = (
3434
apiError = errorPayload;
3535
} else if (typeof errorPayload === "string") {
3636
apiError.message = errorPayload;
37-
apiError.code = error.response?.status || apiError.code;
38-
apiError.type = error.code || apiError.type;
39-
apiError.title = error.response?.statusText || apiError.title;
37+
apiError.code = error.response?.status ?? apiError.code;
38+
apiError.type = error.code ?? apiError.type;
39+
apiError.title = error.response?.statusText ?? apiError.title;
4040
}
4141
}
4242
return apiError;

src/utils/api/api.unit.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ describe("AxiosInstance", () => {
9595

9696
const data = "NOT_FOUND";
9797
const responseError = axiosErrorFactory.build({
98-
response: { data },
98+
response: { data, status: undefined, statusText: undefined },
9999
});
100100

101101
return {
@@ -111,7 +111,7 @@ describe("AxiosInstance", () => {
111111
expect(result).toStrictEqual({
112112
message: "NOT_FOUND",
113113
code: 1,
114-
title: responseError.response?.statusText,
114+
title: "",
115115
type: "Unknown error",
116116
});
117117
});

0 commit comments

Comments
 (0)