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 new file mode 100644 index 0000000..47de9da --- /dev/null +++ b/tests/APIList.test.ts @@ -0,0 +1,64 @@ +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() +})); + +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 diff --git a/tests/APIListVorgang.test.ts b/tests/APIListVorgang.test.ts new file mode 100644 index 0000000..f5267e0 --- /dev/null +++ b/tests/APIListVorgang.test.ts @@ -0,0 +1,136 @@ +import { describe, test, expect, vi } from 'vitest'; +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(), + vorgangNameExists: vi.fn(), + deleteVorgangByToken: vi.fn() +})); + +vi.mock('$lib/minio', () => ({ + client: { + listObjects: vi.fn(), + removeObjects: vi.fn() + } +})); + +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 testCrimesList = []; + + vi.mocked(getCrimesListByToken).mockReturnValueOnce(testCrimesList); + + const response = await GET(MockEvent); + expect(response.status).toBe(200); + + const json = await response.json(); + expect(json).toEqual(testCrimesList); + }); + + test('Vorgang mit Tatorte', async () => { + const testCrimesList = [ + { + 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(testCrimesList); + + const response = await GET(MockEvent); + expect(response.status).toBe(200); + + const json = await response.json(); + 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); + }); +}); 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(); + }); +}); diff --git a/tests/APIUser.test.ts b/tests/APIUser.test.ts new file mode 100644 index 0000000..473ae78 --- /dev/null +++ b/tests/APIUser.test.ts @@ -0,0 +1,40 @@ +import { describe, test, expect } 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); + }); +}); diff --git a/tests/APIUsers.test.ts b/tests/APIUsers.test.ts new file mode 100644 index 0000000..4a9d972 --- /dev/null +++ b/tests/APIUsers.test.ts @@ -0,0 +1,163 @@ +import { describe, test, expect, vi, beforeEach } 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 + + // 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 = { + 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 () => { + // Mocke Parameter und Funktionen von Drittparteien + 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 () => { + // Mocke Parameter und Funktionen von Drittparteien + 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 () => { + // Mocke Parameter und Funktionen von Drittparteien + 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); + }); +}); 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(''); + }); +}); 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'] }