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-2014 Setup new SHD client #1

Merged
merged 26 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e241175
setup client
MarvinOehlerkingCap Aug 1, 2024
8fb2db2
fix lint and workflow
MarvinOehlerkingCap Aug 1, 2024
c9ae46f
fix dockerfile
MarvinOehlerkingCap Aug 1, 2024
72bde9b
fix dockerfile
MarvinOehlerkingCap Aug 1, 2024
c131c4c
fix dockerfile
MarvinOehlerkingCap Aug 1, 2024
c9b497a
fix deployment
MarvinOehlerkingCap Aug 1, 2024
5c20991
fix csp
MarvinOehlerkingCap Aug 1, 2024
2689eb9
fix csp
MarvinOehlerkingCap Aug 1, 2024
ae5e45e
try to use branch deploy script
MarvinOehlerkingCap Aug 1, 2024
4a6d86e
try again
MarvinOehlerkingCap Aug 1, 2024
cf388b3
try again
MarvinOehlerkingCap Aug 1, 2024
ab93b58
try again
MarvinOehlerkingCap Aug 1, 2024
6ad3364
tests
MarvinOehlerkingCap Aug 2, 2024
9e82a5d
fix import
MarvinOehlerkingCap Aug 2, 2024
b9ed1dd
change node to v20, setup sonar
MarvinOehlerkingCap Aug 5, 2024
25d5be6
fix sonar project key
MarvinOehlerkingCap Aug 5, 2024
d5c20c2
adjust sonar, add route guard tests
MarvinOehlerkingCap Aug 5, 2024
e08c515
add integration test for vuetify plugin, remove setup scripts from co…
MarvinOehlerkingCap Aug 5, 2024
4be97a8
exclude root from coverage
MarvinOehlerkingCap Aug 5, 2024
2bc1f7e
exclude webpack scripts and vue files from coverage
MarvinOehlerkingCap Aug 6, 2024
21d57fc
deactivate authentication guard
MarvinOehlerkingCap Aug 6, 2024
d3d5150
adjust generated api
MarvinOehlerkingCap Aug 8, 2024
a269fe9
adjust deployment, fix api tests
MarvinOehlerkingCap Aug 8, 2024
cdc61f4
remove legacy scss styles, change to full webpack build
MarvinOehlerkingCap Aug 12, 2024
1fbd05a
update build command
MarvinOehlerkingCap Aug 12, 2024
41c0d6f
exclude webpack config from coverage
MarvinOehlerkingCap Aug 12, 2024
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
6 changes: 3 additions & 3 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ const config = deepmerge(defaultPreset, {

collectCoverageFrom: [
// Include
"<rootDir>/src/layouts/**/*.{js,ts,vue}",
"<rootDir>/src/modules/**/*.{js,ts,vue}",
"<rootDir>/src/layouts/**/*.{js,ts}", // add vue files
"<rootDir>/src/modules/**/*.{js,ts}", // add vue files
"<rootDir>/src/plugins/**/*.(js|ts)",
// "<rootDir>/src/router/guards/**/*.(js|ts)",
"<rootDir>/src/router/guards/**/*.(js|ts)",
"<rootDir>/src/utils/**/*.(js|ts)",

// Exclude
Expand Down
6 changes: 3 additions & 3 deletions sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ sonar.organization=schulcloud-verbund
sonar.projectKey=hpi-schul-cloud_shd-client

sonar.sources=.
sonar.tests=.
sonar.tests=src/
MarvinOehlerkingCap marked this conversation as resolved.
Show resolved Hide resolved

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

# Include test files in test scope
# Include test files and test utils in test scope
sonar.test.inclusions=tests/**/*,**/*.unit.ts,**/*.unit.js

# Coverage report directory of jest
Expand Down
13 changes: 10 additions & 3 deletions src/modules/page/AboutView.unit.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import { createTestingVuetify } from "@@/tests/test-utils/setup";
import { mount } from "@vue/test-utils";
import { VBtn } from "vuetify/lib/components/index.mjs";
import AboutView from "./AboutView.vue";

describe("AboutView", () => {
const getWrapper = () => {
const wrapper = mount(AboutView);
const wrapper = mount(AboutView, {
global: { plugins: [createTestingVuetify()] },
});

return {
wrapper,
};
};

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

expect(wrapper).toBeDefined();
const counter = wrapper.getComponent(VBtn);
await counter.trigger("click");

expect(counter.text()).toEqual("1");
});
});
7 changes: 6 additions & 1 deletion src/modules/page/AboutView.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
<template>
<main>
<h3>This is the new and improved Superhero-Dashboard!</h3>
<VBtn @click="counter++">{{ counter }}</VBtn>
</main>
</template>

<script setup lang="ts"></script>
<script setup lang="ts">
import { Ref, ref } from "vue";

const counter: Ref<number> = ref(0);
</script>
2 changes: 1 addition & 1 deletion src/router/guards/is-authenticated.guard.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useAuthStore } from "@data/auth";
import { NavigationGuardNext, RouteLocationNormalized } from "vue-router";
import { getLoginUrlWithRedirect } from "../login-redirect-url";
import { getLoginUrlWithRedirect } from "./login-redirect-url";

export const isAuthenticatedGuard = (
to: RouteLocationNormalized,
Expand Down
121 changes: 121 additions & 0 deletions src/router/guards/is-authenticated.unit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { meResponseFactory } from "@@/tests/test-utils/factory";
import { useAuthStore } from "@data/auth";
import { createTestingPinia } from "@pinia/testing";
import { setActivePinia } from "pinia";
import { RouteLocationNormalized } from "vue-router";
import { isAuthenticatedGuard } from "./is-authenticated.guard";

jest.mock("./login-redirect-url", () => ({
getLoginUrlWithRedirect: () => "login-url",
}));

describe("Authentication Guard", () => {
beforeEach(() => {
setActivePinia(createTestingPinia());
});

afterEach(() => {
jest.clearAllMocks();
});

describe("isAuthenticatedGuard", () => {
describe("when authenticated", () => {
const setup = () => {
const to: RouteLocationNormalized = {
fullPath: "/test",
} as RouteLocationNormalized;
const from: RouteLocationNormalized = {} as RouteLocationNormalized;
const next = jest.fn();
const authStore = useAuthStore();

authStore.me = meResponseFactory.build();

return {
to,
from,
next,
};
};

it("should pass", () => {
const { to, from, next } = setup();

isAuthenticatedGuard(to, from, next);

expect(next).toHaveBeenCalled();
});
});

describe("when the url is public", () => {
const setup = () => {
const to: RouteLocationNormalized = {
fullPath: "/test",
meta: {
isPublic: true,
},
} as unknown as RouteLocationNormalized;
const from: RouteLocationNormalized = {} as RouteLocationNormalized;
const next = jest.fn();
const authStore = useAuthStore();

authStore.me = null;

return {
to,
from,
next,
};
};

it("should pass", () => {
const { to, from, next } = setup();

isAuthenticatedGuard(to, from, next);

expect(next).toHaveBeenCalled();
});
});

describe("when not authenticated and the url is not public", () => {
const setup = () => {
const to: RouteLocationNormalized = {
fullPath: "/test",
} as RouteLocationNormalized;
const from: RouteLocationNormalized = {} as RouteLocationNormalized;
const next = jest.fn();
const authStore = useAuthStore();

authStore.me = null;

const assign = jest.fn();
Object.defineProperty(window, "location", {
configurable: true,
value: { assign },
});

return {
to,
from,
next,
assign,
};
};

it("should redirect to login", () => {
const { to, from, next, assign } = setup();

isAuthenticatedGuard(to, from, next);

expect(assign).toHaveBeenCalledWith("login-url");
});

it("should not pass", () => {
const { to, from, next } = setup();

isAuthenticatedGuard(to, from, next);

expect(next).not.toHaveBeenCalled();
});
});
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEnvConfigStore } from "@data/env-config";

export const getLoginUrlWithRedirect = (targetPath: string) => {
export const getLoginUrlWithRedirect = (targetPath: string): string => {
const currentOrigin = window.location.origin;

const currentUrl = new URL(targetPath, currentOrigin);
Expand Down
87 changes: 87 additions & 0 deletions src/router/guards/login-redirect-url.unit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { envsFactory } from "@@/tests/test-utils/factory";
import { useEnvConfigStore } from "@data/env-config";
import { createPinia, setActivePinia } from "pinia";
import { getLoginUrlWithRedirect } from "./login-redirect-url";

describe("Login Redirect Util", () => {
beforeEach(() => {
setActivePinia(createPinia());
});

afterEach(() => {
jest.clearAllMocks();
});

describe("getLoginUrlWithRedirect", () => {
describe("when redirecting to internal login before redirecting to the target url", () => {
const setup = () => {
const loginUrl = "https://test.com/login";
const targetPath = "/dashboard";
const origin = "https://test.com";
const envConfigStore = useEnvConfigStore();

envConfigStore.setEnvs(
envsFactory.build({
NOT_AUTHENTICATED_REDIRECT_URL: loginUrl,
})
);

jest.spyOn(window, "location", "get").mockReturnValue({
origin: origin,
} as Location);

return {
loginUrl,
targetPath,
origin,
};
};

it("should redirect to internal login with post-login-redirect to internal target url", () => {
const { loginUrl, targetPath, origin } = setup();

const result = getLoginUrlWithRedirect(targetPath);

expect(result).toEqual(
`${loginUrl}?redirect=${encodeURIComponent(`${origin}${targetPath}`)}`
);
});
});

describe("when redirecting to an external login before redirecting to the target url", () => {
const setup = () => {
const origin = "https://test.com";
const loginUrl = `https://external-login.thr/login?service=${encodeURIComponent(origin)}`;
const targetPath = "/dashboard";
const envConfigStore = useEnvConfigStore();

envConfigStore.setEnvs(
envsFactory.build({
NOT_AUTHENTICATED_REDIRECT_URL: loginUrl,
})
);

jest.spyOn(window, "location", "get").mockReturnValue({
origin: origin,
} as Location);

return {
loginUrl,
targetPath,
origin,
};
};

it("should redirect to external login with post-login-redirect to internal target url", () => {
const { loginUrl, targetPath, origin } = setup();

const result = getLoginUrlWithRedirect(targetPath);

const redirectUri = encodeURIComponent(`${origin}${targetPath}`);
expect(result).toEqual(
`${loginUrl}${encodeURIComponent(`/?redirect=${redirectUri}`)}`
);
});
});
});
});
6 changes: 3 additions & 3 deletions src/utils/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ export const mapAxiosErrorToResponseError = (
apiError = errorPayload;
} else if (typeof errorPayload === "string") {
apiError.message = errorPayload;
apiError.code = error.response?.status || apiError.code;
apiError.type = error.code || apiError.type;
apiError.title = error.response?.statusText || apiError.title;
apiError.code = error.response?.status ?? apiError.code;
apiError.type = error.code ?? apiError.type;
apiError.title = error.response?.statusText ?? apiError.title;
}
}
return apiError;
Expand Down
4 changes: 2 additions & 2 deletions src/utils/api/api.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ describe("AxiosInstance", () => {

const data = "NOT_FOUND";
const responseError = axiosErrorFactory.build({
response: { data },
response: { data, status: undefined, statusText: undefined },
});

return {
Expand All @@ -111,7 +111,7 @@ describe("AxiosInstance", () => {
expect(result).toStrictEqual({
message: "NOT_FOUND",
code: 1,
title: responseError.response?.statusText,
title: "",
type: "Unknown error",
});
});
Expand Down
Loading