From b43cfe345a75ea9925b9bf288f94d249d75a5fe5 Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Wed, 27 Aug 2025 13:56:39 +0200 Subject: [PATCH 01/16] =?UTF-8?q?configure=20vite.config=20to=20include=20?= =?UTF-8?q?toplevel=20=C2=B4tests=C2=B4=20folder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite.config.ts b/vite.config.ts index 0787e91..e0b01d9 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -13,7 +13,7 @@ export default defineConfig({ name: 'client', environment: 'jsdom', clearMocks: true, - include: ['src/**/*.svelte.{test,spec}.{js,ts}'], + include: ['tests/**/*.{test,spec}.{js,ts}', 'src/**/*.svelte.{test,spec}.{js,ts}'], exclude: ['src/lib/server/**'], setupFiles: ['./vitest-setup-client.ts'] } From cfe00b5424b946f24f35a2904adeaf70cb12e3ac Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Thu, 28 Aug 2025 08:32:29 +0200 Subject: [PATCH 02/16] Add Landing Page View tests, admin and viewer access --- tests/Views.test.ts | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/Views.test.ts diff --git a/tests/Views.test.ts b/tests/Views.test.ts new file mode 100644 index 0000000..f40f52c --- /dev/null +++ b/tests/Views.test.ts @@ -0,0 +1,40 @@ +import { render } from '@testing-library/svelte'; +import { describe, test, expect } from 'vitest'; +import { load } from '../src/routes/(angemeldet)/+layout.server'; +import Homepage from '../src/routes/(angemeldet)/+page.svelte'; + +describe('Homepage-View', () => { + test('Zeige Inhalt an wenn der Nutzer angemeldet ist', () => { + const fakeUser = { id: 'admin' }; + + const { getByText } = render(Homepage, { + props: { + data: { user: fakeUser } + } + }); + + expect(getByText('Benutzerverwaltung')).toBeInTheDocument(); + }); + + // [INFO] Benutzer (ohne Login) kann nicht auf diese Seite zugreifen + // entsprechender Test ist unten `Layout.Server Guard for Homepage-View` +}); + +describe('Layout.Server Guard fuer Homepage-View', () => { + test('Weiterleitung an /anmeldung wenn der Nutzer nicht authentifiziert ist', async () => { + const event = { + url: new URL('http://localhost'), + locals: { + user: null + } + }; + + try { + await load(event); + throw new Error('Expected redirect not thrown'); + } catch (err) { + expect(err.status).toBe(303); + expect(err.location).toBe('/anmeldung'); + } + }); +}); From 0aa49643ca4799d4a4bcea469a749e8295d25d68 Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Thu, 28 Aug 2025 12:03:40 +0200 Subject: [PATCH 03/16] add tests for API endpoint: list vorgang --- tests/APIEndpoints.test.ts | 64 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/APIEndpoints.test.ts diff --git a/tests/APIEndpoints.test.ts b/tests/APIEndpoints.test.ts new file mode 100644 index 0000000..26feb3a --- /dev/null +++ b/tests/APIEndpoints.test.ts @@ -0,0 +1,64 @@ +import { describe, test, expect, vi } from 'vitest'; +import { GET } from '../src/routes/api/list/+server'; + +vi.mock('$lib/server/vorgangService', () => ({ + getVorgaenge: vi.fn() +})); + +import { getVorgaenge } from '$lib/server/vorgangService'; + +const event = { + locals: { + user: { id: 'admin', admin: true } + } +}; + +describe('API-Endpoints: list', () => { + test('Unerlaubter Zugriff', async () => { + const event = { + locals: { + user: null + } + }; + + const response = await GET(event); + expect(response.status).toBe(401); + + const json = await response.json(); + const errorObj = { error: 'Unauthorized' } + expect(json).toEqual(errorObj); + }); + + test('Leere Liste wenn keine Vorgänge existieren', async () => { + vi.mocked(getVorgaenge).mockReturnValueOnce([]); + + const response = await GET(event); + expect(response.status).toBe(200); + + const json = await response.json(); + expect(json).toEqual([]); + }); + + test('Liste mit existierenden Vorgängen', async () => { + const testVorgaenge = [ + { + vorgangToken: '19f1d34e-4f31-48e8-830f-c4e42c29085e', + vorgangName: 'xyz-123', + vorgangPIN: 'pin-123' + }, + { + vorgangToken: '7596e4d5-c51f-482d-a4aa-ff76434305fc', + vorgangName: 'vorgang-2', + vorgangPIN: 'pin-2' + } + ]; + + vi.mocked(getVorgaenge).mockReturnValueOnce(testVorgaenge); + + const response = await GET(event); + expect(response.status).toBe(200); + + const json = await response.json(); + expect(json).toEqual(testVorgaenge); + }); +}); \ No newline at end of file From 9bb691055a0af72ec09eaaa8966cc467f7c213dc Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Thu, 28 Aug 2025 12:09:43 +0200 Subject: [PATCH 04/16] add tests for API endpoint: list vorgang --- tests/{APIEndpoints.test.ts => api-list.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{APIEndpoints.test.ts => api-list.test.ts} (100%) diff --git a/tests/APIEndpoints.test.ts b/tests/api-list.test.ts similarity index 100% rename from tests/APIEndpoints.test.ts rename to tests/api-list.test.ts From a85ef8eab1e58236c5a6355d2feb8372b78d2567 Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Thu, 28 Aug 2025 12:40:56 +0200 Subject: [PATCH 05/16] rename api-list test file to use CamelCase --- tests/{api-list.test.ts => APIList.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{api-list.test.ts => APIList.test.ts} (100%) diff --git a/tests/api-list.test.ts b/tests/APIList.test.ts similarity index 100% rename from tests/api-list.test.ts rename to tests/APIList.test.ts From 66a4c014ad81cdaa9fc3467ce865e6dfdd203bfe Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Thu, 28 Aug 2025 12:43:56 +0200 Subject: [PATCH 06/16] add tests for API endpoint: list/[vorgang] --- tests/APIListVorgang.test.ts | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 tests/APIListVorgang.test.ts diff --git a/tests/APIListVorgang.test.ts b/tests/APIListVorgang.test.ts new file mode 100644 index 0000000..9eed193 --- /dev/null +++ b/tests/APIListVorgang.test.ts @@ -0,0 +1,73 @@ +import { describe, test, expect, vi } from 'vitest'; +import { GET } from '../src/routes/api/list/[vorgang]/+server'; + +vi.mock('$lib/server/vorgangService', () => ({ + getCrimesListByToken: vi.fn() +})); + +import { getCrimesListByToken } from '$lib/server/vorgangService'; + +const MockEvent = { + params: { vorgang: '123' }, + locals: { + user: { id: 'admin', admin: true } + } +}; + +describe('API-Endpoints: list/[vorgang]', () => { + test('Unerlaubter Zugriff', async () => { + const event = { + locals: { + user: null + } + }; + + const response = await GET(event); + expect(response.status).toBe(401); + + const json = await response.json(); + const errorObj = { error: 'Unauthorized' }; + expect(json).toEqual(errorObj); + }); + + test('Vorgang ohne Tatorte', async () => { + const testTatorte = []; + + vi.mocked(getCrimesListByToken).mockReturnValueOnce(testTatorte); + + const response = await GET(MockEvent); + expect(response.status).toBe(200); + + const json = await response.json(); + expect(json).toEqual(testTatorte); + }); + + test('Vorgang mit Tatorte', async () => { + const testTatorte = [ + { + name: 'model-A', + lastModified: '2025-08-28T09:44:12.453Z', + etag: '558f35716f6af953f9bb5d75f6d77e6a', + size: 8947140, + prefix: '7596e4d5-c51f-482d-a4aa-ff76434305fc', + show_button: true + }, + { + name: 'model-z', + lastModified: '2025-08-28T10:37:20.142Z', + etag: '43e3989c32c4682bee407baaf83b6fa0', + size: 35788560, + prefix: '7596e4d5-c51f-482d-a4aa-ff76434305fc', + show_button: true + } + ]; + + vi.mocked(getCrimesListByToken).mockReturnValueOnce(testTatorte); + + const response = await GET(MockEvent); + expect(response.status).toBe(200); + + const json = await response.json(); + expect(json).toEqual(testTatorte); + }); +}); From 52bffcd4f0126c8168b2d5e2702410ffb6a1d023 Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Fri, 29 Aug 2025 08:25:52 +0200 Subject: [PATCH 07/16] reorder import, vi.mock is being hoisted --- src/{index.test.js => index.test.js} | 0 tests/APIList.test.ts | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/{index.test.js => index.test.js} (100%) diff --git a/src/index.test.js b/src/index.test.js similarity index 100% rename from src/index.test.js rename to src/index.test.js diff --git a/tests/APIList.test.ts b/tests/APIList.test.ts index 26feb3a..47de9da 100644 --- a/tests/APIList.test.ts +++ b/tests/APIList.test.ts @@ -1,12 +1,12 @@ import { describe, test, expect, vi } from 'vitest'; import { GET } from '../src/routes/api/list/+server'; +import { getVorgaenge } from '$lib/server/vorgangService'; +// Mocks vi.mock('$lib/server/vorgangService', () => ({ getVorgaenge: vi.fn() })); -import { getVorgaenge } from '$lib/server/vorgangService'; - const event = { locals: { user: { id: 'admin', admin: true } From 9be9676f6cc8fb0f856edf94974e618ac4c213e0 Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Fri, 29 Aug 2025 10:19:45 +0200 Subject: [PATCH 08/16] add more tests for API endpoint: list/[vorgang], HEAD and DELETE --- tests/APIListVorgang.test.ts | 81 ++++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/tests/APIListVorgang.test.ts b/tests/APIListVorgang.test.ts index 9eed193..f5267e0 100644 --- a/tests/APIListVorgang.test.ts +++ b/tests/APIListVorgang.test.ts @@ -1,11 +1,26 @@ import { describe, test, expect, vi } from 'vitest'; -import { GET } from '../src/routes/api/list/[vorgang]/+server'; +import { DELETE, GET, HEAD } from '../src/routes/api/list/[vorgang]/+server'; +import { + getCrimesListByToken, + vorgangNameExists, + deleteVorgangByToken +} from '$lib/server/vorgangService'; +import { client } from '$lib/minio'; +import { EventEmitter } from 'events'; +// Mocks vi.mock('$lib/server/vorgangService', () => ({ - getCrimesListByToken: vi.fn() + getCrimesListByToken: vi.fn(), + vorgangNameExists: vi.fn(), + deleteVorgangByToken: vi.fn() })); -import { getCrimesListByToken } from '$lib/server/vorgangService'; +vi.mock('$lib/minio', () => ({ + client: { + listObjects: vi.fn(), + removeObjects: vi.fn() + } +})); const MockEvent = { params: { vorgang: '123' }, @@ -31,19 +46,19 @@ describe('API-Endpoints: list/[vorgang]', () => { }); test('Vorgang ohne Tatorte', async () => { - const testTatorte = []; + const testCrimesList = []; - vi.mocked(getCrimesListByToken).mockReturnValueOnce(testTatorte); + vi.mocked(getCrimesListByToken).mockReturnValueOnce(testCrimesList); const response = await GET(MockEvent); expect(response.status).toBe(200); const json = await response.json(); - expect(json).toEqual(testTatorte); + expect(json).toEqual(testCrimesList); }); test('Vorgang mit Tatorte', async () => { - const testTatorte = [ + const testCrimesList = [ { name: 'model-A', lastModified: '2025-08-28T09:44:12.453Z', @@ -62,12 +77,60 @@ describe('API-Endpoints: list/[vorgang]', () => { } ]; - vi.mocked(getCrimesListByToken).mockReturnValueOnce(testTatorte); + vi.mocked(getCrimesListByToken).mockReturnValueOnce(testCrimesList); const response = await GET(MockEvent); expect(response.status).toBe(200); const json = await response.json(); - expect(json).toEqual(testTatorte); + expect(json).toEqual(testCrimesList); + }); + + test('Vorgang existiert via HEAD', async () => { + const vorgangExists = true; + vi.mocked(vorgangNameExists).mockReturnValueOnce(vorgangExists); + + const response = await HEAD(MockEvent); + expect(response.status).toBe(200); + + const textContent = await response.text(); + expect(textContent).toEqual(''); + }); + + test('Vorgang existiert nicht via HEAD', async () => { + const vorgangExists = false; + vi.mocked(vorgangNameExists).mockReturnValueOnce(vorgangExists); + const response = await HEAD(MockEvent); + + expect(response.status).toBe(404); + const textContent = await response.text(); + + expect(textContent).toEqual(''); + }); + + test('Lösche Vorgang und dazugehörige S3 Objekte', async () => { + // Mock data + const fakeStream = new EventEmitter(); + vi.mocked(client.listObjects).mockReturnValue(fakeStream); + vi.mocked(client.removeObjects).mockResolvedValue(undefined); + vi.mocked(deleteVorgangByToken).mockReturnValueOnce(undefined); + + const responsePromise = DELETE(MockEvent); + const fakeCrimeNames = [ + `${MockEvent.params.vorgang}/file1.glb`, + `${MockEvent.params.vorgang}/file2.glb` + ]; + + // simulate data stream + fakeStream.emit('data', { name: fakeCrimeNames[0] }); + fakeStream.emit('data', { name: fakeCrimeNames[1] }); + fakeStream.emit('end'); + + const response = await responsePromise; + + expect(client.removeObjects).toHaveBeenCalledWith('tatort', fakeCrimeNames); + expect(deleteVorgangByToken).toHaveBeenCalledWith(MockEvent.params.vorgang); + + expect(response.status).toBe(204); }); }); From 418e91c504067c06724b7fb3573033f17a8183b8 Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Tue, 2 Sep 2025 10:47:08 +0200 Subject: [PATCH 09/16] add tests for API endpoint: list/[vorgang]/[tatort] --- tests/APIListVorgangTatort.test.ts | 93 ++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tests/APIListVorgangTatort.test.ts diff --git a/tests/APIListVorgangTatort.test.ts b/tests/APIListVorgangTatort.test.ts new file mode 100644 index 0000000..08e5db5 --- /dev/null +++ b/tests/APIListVorgangTatort.test.ts @@ -0,0 +1,93 @@ +import { describe, test, expect, vi } from 'vitest'; +import { DELETE, PUT } from '../src/routes/api/list/[vorgang]/[tatort]/+server'; +import { BUCKET, client } from '$lib/minio'; + +// Mock data and methods +const fakeVorgangToken = `c399423a-ba37-4fe1-bbdf-80e5881168ff`; +const fakeCrimeOldName = `model-A`; +const fakeCrimeNewName = 'model-Z'; +const fakeCrimePath = `${fakeVorgangToken}/${fakeCrimeOldName}`; +const fullFakeCrimePath = `/${BUCKET}/${fakeCrimePath}`; +const fakeCrimeAPIURL = `http://localhost:5173/api/list/${fakeCrimePath}`; + +vi.mock('$lib/minio', () => ({ + client: { + removeObject: vi.fn(), + statObject: vi.fn(), + copyObject: vi.fn() + }, + BUCKET: 'tatort' +})); + +describe('API-Endpoints: list/[vorgang]/[tatort]', () => { + test('Löschen von Tatorten', async () => { + const request = new Request(fakeCrimeAPIURL); + const response = await DELETE({ request }); + + expect(client.removeObject).toHaveBeenCalledWith(BUCKET, fakeCrimePath); + + expect(response.status).toBe(204); + const responseBody = await response.text(); + expect(responseBody).toBe(''); + }); + + test('Umbennen von Tatorten: Erfolgreich', async () => { + const request = new Request(fakeCrimeAPIURL, { + method: 'PUT', + body: JSON.stringify({ + oldName: fakeCrimeOldName, + newName: fakeCrimeNewName + }) + }); + const params = { vorgang: fakeVorgangToken }; + + // Mock Datei nicht gefunden + client.statObject.mockRejectedValueOnce(new Error('NotFound')); + + const response = await PUT({ params, request }); + + const fakeCrimeNewPath = `${fakeVorgangToken}/${fakeCrimeNewName}`; + expect(client.statObject).toHaveBeenCalledWith(BUCKET, fakeCrimeNewPath); + expect(client.copyObject).toHaveBeenCalledWith(BUCKET, fakeCrimeNewPath, fullFakeCrimePath); + expect(client.removeObject).toHaveBeenCalledWith(BUCKET, fakeCrimePath); + + expect(response.status).toBe(200); + }); + + test('Umbennen von Tatorten: Fehlende(r) Name', async () => { + const request = new Request(fakeCrimeAPIURL, { + method: 'PUT', + body: JSON.stringify({ + oldName: '', + newName: '' + }) + }); + const params = { vorgang: fakeVorgangToken }; + + const response = await PUT({ params, request }); + expect(response.status).toBe(400); + }); + + test('Umbennen von Tatorten: Existierender Name', async () => { + const request = new Request(fakeCrimeAPIURL, { + method: 'PUT', + body: JSON.stringify({ + oldName: fakeCrimeOldName, + newName: fakeCrimeNewName + }) + }); + const params = { vorgang: fakeVorgangToken }; + + // Datei existiert bereits + client.statObject.mockResolvedValueOnce({}); + + const response = await PUT({ params, request }); + + expect(response.status).toBe(400); + + const fakeCrimeNewPath = `${fakeVorgangToken}/${fakeCrimeNewName}`; + expect(client.statObject).toHaveBeenCalledWith(BUCKET, fakeCrimeNewPath); + expect(client.copyObject).not.toHaveBeenCalled(); + expect(client.removeObject).not.toHaveBeenCalled(); + }); +}); From 877c350824d0645e5c76a6a0cf6ab9701ba8eecc Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Tue, 2 Sep 2025 10:58:38 +0200 Subject: [PATCH 10/16] rename test file for ViewAngemeldet --- tests/{Views.test.ts => ViewAngemeldet.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{Views.test.ts => ViewAngemeldet.test.ts} (100%) diff --git a/tests/Views.test.ts b/tests/ViewAngemeldet.test.ts similarity index 100% rename from tests/Views.test.ts rename to tests/ViewAngemeldet.test.ts From c222d75ac516378b26eb5199fc246827873baf1b Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Tue, 2 Sep 2025 11:10:07 +0200 Subject: [PATCH 11/16] add tests for API endpoint: user --- tests/APIUser.test.ts | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/APIUser.test.ts diff --git a/tests/APIUser.test.ts b/tests/APIUser.test.ts new file mode 100644 index 0000000..352d13c --- /dev/null +++ b/tests/APIUser.test.ts @@ -0,0 +1,40 @@ +import { describe, test, expect, vi } from 'vitest'; +import { GET } from '../src/routes/api/user/+server'; + +const id = 'admin'; + +describe('API-Endpoints: User ist Admin', () => { + test('User ist Admin', async () => { + const admin = true; + const event = { + locals: { + user: { id, admin } + } + }; + + const fakeResult = { admin }; + + const response = await GET(event); + expect(response.status).toBe(200); + + const json = await response.json(); + expect(json).toEqual(fakeResult); + }); + + test('User ist kein Admin', async () => { + const admin = false; + const event = { + locals: { + user: { id, admin } + } + }; + + const fakeResult = { admin }; + + const response = await GET(event); + expect(response.status).toBe(200); + + const json = await response.json(); + expect(json).toEqual(fakeResult); + }); +}); From 1e96f13d22feb7e1d179bbc15ac6a1ae858c5920 Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Tue, 2 Sep 2025 11:49:11 +0200 Subject: [PATCH 12/16] remove unused import --- tests/APIUser.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/APIUser.test.ts b/tests/APIUser.test.ts index 352d13c..473ae78 100644 --- a/tests/APIUser.test.ts +++ b/tests/APIUser.test.ts @@ -1,4 +1,4 @@ -import { describe, test, expect, vi } from 'vitest'; +import { describe, test, expect } from 'vitest'; import { GET } from '../src/routes/api/user/+server'; const id = 'admin'; From e0b490a353c8bf37f9a988a12d4c7dc02415946d Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Wed, 3 Sep 2025 08:31:53 +0200 Subject: [PATCH 13/16] add tests for API endpoint: users --- tests/APIUsers.test.ts | 172 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 tests/APIUsers.test.ts diff --git a/tests/APIUsers.test.ts b/tests/APIUsers.test.ts new file mode 100644 index 0000000..ae8a607 --- /dev/null +++ b/tests/APIUsers.test.ts @@ -0,0 +1,172 @@ +import { describe, test, expect, vi } from 'vitest'; +import { GET, POST } from '../src/routes/api/users/+server'; +import bcrypt from 'bcrypt'; + +import { addUser, getUsers } from '$lib/server/userService'; + +vi.mock('$lib/server/userService', () => ({ + addUser: vi.fn(), + getUsers: vi.fn() +})); + +vi.mock('bcrypt', () => ({ + default: { + hashSync: vi.fn() + } +})); + +describe('API-Endpoint: Users', () => { + test('Unerlaubter Zugriff', async () => { + const event = { + locals: { + user: null + } + }; + + const response = await GET(event); + expect(response.status).toBe(401); + + const errorMessage = { error: 'Unauthorized' }; + const json = await response.json(); + expect(json).toEqual(errorMessage); + }); + + // [INFO] Test auf keine User nicht notwendig, da immer min. ein User vorhanden + + test('Rufe Liste aller User ab', async () => { + const fakeLoggedInUser = { id: 'admin', admin: true }; + const event = { + locals: { + user: fakeLoggedInUser + } + }; + + const fakeResult = [{ userId: 42, userName: 'admin' }]; + getUsers.mockReturnValueOnce(fakeResult); + + const response = await GET(event); + expect(response.status).toBe(200); + + const json = await response.json(); + expect(json).toEqual(fakeResult); + }); + + test('Füge Benutzer hinzu: Erfolgreich', async () => { + // mock params and third party function calls + const fakeLoggedInUser = { id: 'admin', admin: true }; + const mockLocals = { + user: fakeLoggedInUser + }; + + const fakeUsersAPIURL = `http://localhost:5173/api/users`; + const fakeUserID = 42; + const fakeUsername = 'admin'; + const fakeUserPassword = 'pass-123'; + + const mockRequest = new Request(fakeUsersAPIURL, { + method: 'POST', + body: JSON.stringify({ + userName: fakeUsername, + userPassword: fakeUserPassword + }) + }); + + const mockedHash = 'mocked-hash'; + bcrypt.hashSync.mockReturnValueOnce(mockedHash); + + const mockedRowInfo = { + changes: 1, + lastInsertRowid: fakeUserID + }; + addUser.mockReturnValueOnce(mockedRowInfo); + + const response = await POST({ + request: mockRequest, + locals: mockLocals + }); + + expect(response.status).toBe(201); + + const fakeResult = { userId: fakeUserID, userName: fakeUsername }; + const json = await response.json(); + expect(json).toEqual(fakeResult); + + expect(addUser).toHaveBeenCalledWith(fakeUsername, mockedHash); + }); + + test('Füge Benutzer hinzu: Fehlender Name oder Passwort', async () => { + // mock params and third party function calls + const fakeLoggedInUser = { id: 'admin', admin: true }; + const mockLocals = { + user: fakeLoggedInUser + }; + + const fakeUsersAPIURL = `http://localhost:5173/api/users`; + const fakeUserID = 42; + const fakeUsername = ''; + const fakeUserPassword = ''; + + const mockRequest = new Request(fakeUsersAPIURL, { + method: 'POST', + body: JSON.stringify({ + userName: fakeUsername, + userPassword: fakeUserPassword + }) + }); + + const response = await POST({ + request: mockRequest, + locals: mockLocals + }); + + expect(response.status).toBe(400); + + const errorMessage = { error: 'Missing input' }; + const json = await response.json(); + expect(json).toEqual(errorMessage); + + expect(addUser).not.toHaveBeenCalled(); + }); + + test('Füge Benutzer hinzu: Nicht erfolgreich, keine Datenbankänderung', async () => { + // mock params and third party function calls + const fakeLoggedInUser = { id: 'admin', admin: true }; + const mockLocals = { + user: fakeLoggedInUser + }; + + const fakeUsersAPIURL = `http://localhost:5173/api/users`; + const fakeUserID = 42; + const fakeUsername = 'admin'; + const fakeUserPassword = 'pass-123'; + + const mockRequest = new Request(fakeUsersAPIURL, { + method: 'POST', + body: JSON.stringify({ + userName: fakeUsername, + userPassword: fakeUserPassword + }) + }); + + const mockedHash = 'mocked-hash'; + bcrypt.hashSync.mockReturnValueOnce(mockedHash); + + const mockedRowInfo = { + changes: 0, + lastInsertRowid: fakeUserID + }; + addUser.mockReturnValueOnce(mockedRowInfo); + + const response = await POST({ + request: mockRequest, + locals: mockLocals + }); + + expect(response.status).toBe(400); + + const body = await response.text(); + expect(body).toEqual(''); + + expect(addUser).toHaveBeenCalledWith(fakeUsername, mockedHash); + }); +}); From 821b8a64405f903ad5b7371696b69d133be10f7c Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Wed, 3 Sep 2025 08:41:57 +0200 Subject: [PATCH 14/16] refactoring: extract common locals.user definition and translatation of comments --- tests/APIUsers.test.ts | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/tests/APIUsers.test.ts b/tests/APIUsers.test.ts index ae8a607..4a9d972 100644 --- a/tests/APIUsers.test.ts +++ b/tests/APIUsers.test.ts @@ -1,4 +1,4 @@ -import { describe, test, expect, vi } from 'vitest'; +import { describe, test, expect, vi, beforeEach } from 'vitest'; import { GET, POST } from '../src/routes/api/users/+server'; import bcrypt from 'bcrypt'; @@ -33,6 +33,12 @@ describe('API-Endpoint: Users', () => { // [INFO] Test auf keine User nicht notwendig, da immer min. ein User vorhanden + // Mock eingelogter User bzw. stelle locals.user zur Verfügung + const fakeLoggedInUser = { id: 'admin', admin: true }; + const mockLocals = { + user: fakeLoggedInUser + }; + test('Rufe Liste aller User ab', async () => { const fakeLoggedInUser = { id: 'admin', admin: true }; const event = { @@ -52,12 +58,7 @@ describe('API-Endpoint: Users', () => { }); test('Füge Benutzer hinzu: Erfolgreich', async () => { - // mock params and third party function calls - const fakeLoggedInUser = { id: 'admin', admin: true }; - const mockLocals = { - user: fakeLoggedInUser - }; - + // Mocke Parameter und Funktionen von Drittparteien const fakeUsersAPIURL = `http://localhost:5173/api/users`; const fakeUserID = 42; const fakeUsername = 'admin'; @@ -95,12 +96,7 @@ describe('API-Endpoint: Users', () => { }); test('Füge Benutzer hinzu: Fehlender Name oder Passwort', async () => { - // mock params and third party function calls - const fakeLoggedInUser = { id: 'admin', admin: true }; - const mockLocals = { - user: fakeLoggedInUser - }; - + // Mocke Parameter und Funktionen von Drittparteien const fakeUsersAPIURL = `http://localhost:5173/api/users`; const fakeUserID = 42; const fakeUsername = ''; @@ -129,12 +125,7 @@ describe('API-Endpoint: Users', () => { }); test('Füge Benutzer hinzu: Nicht erfolgreich, keine Datenbankänderung', async () => { - // mock params and third party function calls - const fakeLoggedInUser = { id: 'admin', admin: true }; - const mockLocals = { - user: fakeLoggedInUser - }; - + // Mocke Parameter und Funktionen von Drittparteien const fakeUsersAPIURL = `http://localhost:5173/api/users`; const fakeUserID = 42; const fakeUsername = 'admin'; From f18ef0711647a4836b58e9c53ae1b34c633323c6 Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Wed, 3 Sep 2025 10:16:18 +0200 Subject: [PATCH 15/16] add tests for API endpoint: vorgang/[vorgang]/vorgangPIN --- tests/APIVorgangVorgangVorgangPIN.test.ts | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/APIVorgangVorgangVorgangPIN.test.ts diff --git a/tests/APIVorgangVorgangVorgangPIN.test.ts b/tests/APIVorgangVorgangVorgangPIN.test.ts new file mode 100644 index 0000000..6357f3a --- /dev/null +++ b/tests/APIVorgangVorgangVorgangPIN.test.ts @@ -0,0 +1,43 @@ +import { describe, test, expect, vi } from 'vitest'; +import { GET } from '../src/routes/api/vorgang/[vorgang]/vorgangPIN/+server'; +import { db } from '$lib/server/dbService'; + +const mockEvent = { + params: { vorgang: '123' } +}; + +vi.mock('$lib/server/dbService', () => ({ + db: { + prepare: vi.fn() + } +})); + +describe('API-Endpoint: Vorgang-PIN', () => { + test('Vorgang PIN: Erfolgreich', async () => { + // only interested in PIN value + const mockPIN = 'pin-123'; + const mockRow = { pin: mockPIN }; + + const getMock = vi.fn().mockReturnValue(mockRow); + + db.prepare.mockReturnValue({ get: getMock }); + const response = await GET(mockEvent); + expect(response.status).toBe(200); + + const body = await response.text(); + expect(body).toEqual(mockPIN); + }); + + test('Vorgang PIN: Nicht erfolgreich', async () => { + const mockRow = {}; + + const getMock = vi.fn().mockReturnValue(mockRow); + + db.prepare.mockReturnValue({ get: getMock }); + const response = await GET(mockEvent); + expect(response.status).toBe(404); + + const body = await response.text(); + expect(body).toEqual(''); + }); +}); From 1e85df91274079a6fcc2544de1e2f7ce4d170bee Mon Sep 17 00:00:00 2001 From: Chi Cong Tran Date: Thu, 4 Sep 2025 09:59:46 +0200 Subject: [PATCH 16/16] remove unrelated View tests --- tests/ViewAngemeldet.test.ts | 40 ------------------------------------ 1 file changed, 40 deletions(-) delete mode 100644 tests/ViewAngemeldet.test.ts diff --git a/tests/ViewAngemeldet.test.ts b/tests/ViewAngemeldet.test.ts deleted file mode 100644 index f40f52c..0000000 --- a/tests/ViewAngemeldet.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { render } from '@testing-library/svelte'; -import { describe, test, expect } from 'vitest'; -import { load } from '../src/routes/(angemeldet)/+layout.server'; -import Homepage from '../src/routes/(angemeldet)/+page.svelte'; - -describe('Homepage-View', () => { - test('Zeige Inhalt an wenn der Nutzer angemeldet ist', () => { - const fakeUser = { id: 'admin' }; - - const { getByText } = render(Homepage, { - props: { - data: { user: fakeUser } - } - }); - - expect(getByText('Benutzerverwaltung')).toBeInTheDocument(); - }); - - // [INFO] Benutzer (ohne Login) kann nicht auf diese Seite zugreifen - // entsprechender Test ist unten `Layout.Server Guard for Homepage-View` -}); - -describe('Layout.Server Guard fuer Homepage-View', () => { - test('Weiterleitung an /anmeldung wenn der Nutzer nicht authentifiziert ist', async () => { - const event = { - url: new URL('http://localhost'), - locals: { - user: null - } - }; - - try { - await load(event); - throw new Error('Expected redirect not thrown'); - } catch (err) { - expect(err.status).toBe(303); - expect(err.location).toBe('/anmeldung'); - } - }); -});