import { beforeEach, describe, expect, it } from 'vitest' import { findActiveClientByTokenHash, listClients } from '~/data/repositories/clients' import { performCreateClient, performRevokeClient, tokenPrefix } from '~/lib/admin-clients' import { hashToken } from '~/lib/api-auth' import { adminViewer, auditEntries, form, makeBrand, makeProject, moderatorViewer, resetAccounts, } from '../support/admin' beforeEach(resetAccounts) describe('performCreateClient', () => { it('legt einen Zugang an, zeigt das Token einmal und speichert nur den Hash', async () => { const brand = await makeBrand() const project = await makeProject(brand.id) const state = await performCreateClient( adminViewer(), form({ name: 'Trakk Web', mode: 'read', scope: 'customer', projectId: project.id }), ) expect(state.status).toBe('ok') expect(state.token?.startsWith(tokenPrefix)).toBe(true) const clients = await listClients() expect(clients).toHaveLength(1) expect(clients[0]).toMatchObject({ name: 'Trakk Web', mode: 'read', scope: 'customer', projectId: project.id, canPublish: false, revokedAt: null, }) expect(clients[0]?.tokenHash).not.toBe(state.token) expect(clients[0]?.tokenHash).toBe(hashToken(state.token!)) const found = await findActiveClientByTokenHash(hashToken(state.token!)) expect(found?.id).toBe(clients[0]?.id) }) it('protokolliert das Anlegen ohne das Token', async () => { await performCreateClient(adminViewer(), form({ name: 'Trakk Web', mode: 'read', scope: 'customer' })) const entries = await auditEntries() expect(entries).toHaveLength(1) expect(entries[0]).toMatchObject({ action: 'client.create', entity: 'api_client' }) expect(JSON.stringify(entries[0]?.data)).not.toContain(tokenPrefix) }) it('lässt die Projektbindung leer, wenn kein Projekt gewählt ist', async () => { const state = await performCreateClient( adminViewer(), form({ name: 'Alle Projekte', mode: 'read', scope: 'internal', projectId: '' }), ) expect(state.status).toBe('ok') expect((await listClients())[0]?.projectId).toBeNull() }) it('lehnt ein unbekanntes Projekt ab', async () => { const state = await performCreateClient( adminViewer(), form({ name: 'Fremd', mode: 'read', scope: 'customer', projectId: '11111111-1111-4111-8111-111111111111', }), ) expect(state.fields?.projectId).toBe('projectUnknown') expect(await listClients()).toHaveLength(0) }) it('lehnt einen unbekannten Modus ab', async () => { const state = await performCreateClient( adminViewer(), form({ name: 'Fremd', mode: 'alles', scope: 'customer' }), ) expect(state.fields?.mode).toBe('modeInvalid') }) it('lässt einen Moderator keinen Zugang anlegen', async () => { const brand = await makeBrand() const project = await makeProject(brand.id) const state = await performCreateClient( moderatorViewer([project.id]), form({ name: 'Eigener Zugang', mode: 'read', scope: 'customer', projectId: project.id }), ) expect(state.message).toBe('forbidden') expect(await listClients()).toHaveLength(0) expect(await auditEntries()).toHaveLength(0) }) }) describe('performRevokeClient', () => { it('widerruft einen Zugang und protokolliert das', async () => { const created = await performCreateClient( adminViewer(), form({ name: 'Trakk Web', mode: 'read', scope: 'customer' }), ) const state = await performRevokeClient(adminViewer(), form({ id: created.id! })) expect(state.status).toBe('ok') const clients = await listClients() expect(clients[0]?.revokedAt).toBeInstanceOf(Date) expect((await auditEntries())[0]?.action).toBe('client.revoke') }) it('lässt ein widerrufenes Token nicht mehr durch', async () => { const created = await performCreateClient( adminViewer(), form({ name: 'Trakk Web', mode: 'read', scope: 'customer' }), ) await performRevokeClient(adminViewer(), form({ id: created.id! })) expect(await findActiveClientByTokenHash(hashToken(created.token!))).toBeUndefined() }) it('meldet einen zweiten Widerruf als unbekannt', async () => { const created = await performCreateClient( adminViewer(), form({ name: 'Trakk Web', mode: 'read', scope: 'customer' }), ) await performRevokeClient(adminViewer(), form({ id: created.id! })) const state = await performRevokeClient(adminViewer(), form({ id: created.id! })) expect(state.message).toBe('notFound') }) it('lässt einen Moderator keinen Zugang widerrufen', async () => { const created = await performCreateClient( adminViewer(), form({ name: 'Trakk Web', mode: 'read', scope: 'customer' }), ) const state = await performRevokeClient(moderatorViewer(), form({ id: created.id! })) expect(state.message).toBe('forbidden') expect((await listClients())[0]?.revokedAt).toBeNull() }) })