Files
waggle-os/packages/server/tests/kvark/kvark-auth.test.ts
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

102 lines
3.6 KiB
TypeScript

/**
* KVARK Auth — tests with mocked fetch.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { KvarkAuth } from '../../src/kvark/kvark-auth.js';
import { KvarkAuthError, KvarkUnavailableError } from '../../src/kvark/kvark-types.js';
function mockFetch(responses: Array<{ status: number; body: unknown }>): typeof globalThis.fetch {
let callIndex = 0;
return vi.fn(async () => {
const resp = responses[callIndex++] ?? { status: 500, body: { detail: 'No mock response' } };
return new Response(JSON.stringify(resp.body), {
status: resp.status,
headers: { 'Content-Type': 'application/json' },
});
}) as unknown as typeof globalThis.fetch;
}
const BASE_CONFIG = { baseUrl: 'http://kvark:8000', identifier: 'admin', password: 'secret' };
describe('KvarkAuth', () => {
it('login calls POST /api/auth/login and returns token', async () => {
const fetch = mockFetch([{
status: 200,
body: { success: true, access_token: 'jwt-123', token_type: 'bearer', user: { id: 1, identifier: 'admin' }, error: null },
}]);
const auth = new KvarkAuth(BASE_CONFIG, fetch);
const token = await auth.login();
expect(token).toBe('jwt-123');
expect(auth.hasToken).toBe(true);
expect(fetch).toHaveBeenCalledOnce();
const [url, opts] = (fetch as ReturnType<typeof vi.fn>).mock.calls[0];
expect(url).toBe('http://kvark:8000/api/auth/login');
expect(opts.method).toBe('POST');
expect(JSON.parse(opts.body)).toEqual({ identifier: 'admin', password: 'secret' });
});
it('getToken returns cached token without re-login', async () => {
const fetch = mockFetch([{
status: 200,
body: { success: true, access_token: 'jwt-cached', token_type: 'bearer', user: null, error: null },
}]);
const auth = new KvarkAuth(BASE_CONFIG, fetch);
const t1 = await auth.getToken();
const t2 = await auth.getToken();
expect(t1).toBe('jwt-cached');
expect(t2).toBe('jwt-cached');
expect(fetch).toHaveBeenCalledOnce(); // only one login call
});
it('invalidate clears token, next getToken re-logins', async () => {
const fetch = mockFetch([
{ status: 200, body: { success: true, access_token: 'token-1', token_type: 'bearer', user: null, error: null } },
{ status: 200, body: { success: true, access_token: 'token-2', token_type: 'bearer', user: null, error: null } },
]);
const auth = new KvarkAuth(BASE_CONFIG, fetch);
const t1 = await auth.getToken();
expect(t1).toBe('token-1');
auth.invalidate();
expect(auth.hasToken).toBe(false);
const t2 = await auth.getToken();
expect(t2).toBe('token-2');
expect(fetch).toHaveBeenCalledTimes(2);
});
it('throws KvarkAuthError on login failure', async () => {
const fetch = mockFetch([{
status: 401,
body: { detail: 'Invalid credentials' },
}]);
const auth = new KvarkAuth(BASE_CONFIG, fetch);
await expect(auth.login()).rejects.toThrow(KvarkAuthError);
});
it('throws KvarkAuthError when success=false in response', async () => {
const fetch = mockFetch([{
status: 200,
body: { success: false, access_token: null, token_type: 'bearer', user: null, error: 'Account disabled' },
}]);
const auth = new KvarkAuth(BASE_CONFIG, fetch);
await expect(auth.login()).rejects.toThrow('Account disabled');
});
it('throws KvarkUnavailableError on network failure', async () => {
const fetch = vi.fn(async () => { throw new Error('ECONNREFUSED'); }) as unknown as typeof globalThis.fetch;
const auth = new KvarkAuth(BASE_CONFIG, fetch);
await expect(auth.login()).rejects.toThrow(KvarkUnavailableError);
});
});