2026-06-26 17:48:24 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Settings API Tests – Key/Value settings (Get / Put / Upsert)
|
|
|
|
|
|
*/
|
|
|
|
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
|
|
|
|
import type { FastifyInstance } from 'fastify';
|
|
|
|
|
|
import { SqliteAdapter } from '../src/database/SqliteAdapter.js';
|
|
|
|
|
|
import { createServer } from '../src/server.js';
|
|
|
|
|
|
|
|
|
|
|
|
describe('Settings API', () => {
|
|
|
|
|
|
let app: FastifyInstance;
|
|
|
|
|
|
let db: SqliteAdapter;
|
2026-07-04 16:43:55 +02:00
|
|
|
|
let authToken: string;
|
2026-06-26 17:48:24 +02:00
|
|
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
|
|
db = new SqliteAdapter(':memory:');
|
|
|
|
|
|
await db.init();
|
|
|
|
|
|
app = await createServer({ db, port: 0 });
|
|
|
|
|
|
await app.ready();
|
2026-07-04 16:43:55 +02:00
|
|
|
|
|
|
|
|
|
|
const regRes = await app.inject({
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
url: '/api/auth/register',
|
|
|
|
|
|
payload: { email: 'admin-test@example.com', password: 'Password123!', name: 'Admin Test', role: 'admin' },
|
|
|
|
|
|
});
|
|
|
|
|
|
authToken = JSON.parse(regRes.body).session.token;
|
2026-06-26 17:48:24 +02:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
|
|
await app.close();
|
|
|
|
|
|
db.close();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-07-04 16:43:55 +02:00
|
|
|
|
// ─── Auth Check ──────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
describe('Authentication', () => {
|
|
|
|
|
|
it('should return 401 without authorization header', async () => {
|
|
|
|
|
|
const response = await app.inject({
|
|
|
|
|
|
method: 'GET',
|
|
|
|
|
|
url: '/api/settings/non-existent-key',
|
|
|
|
|
|
});
|
|
|
|
|
|
expect(response.statusCode).toBe(401);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-06-26 17:48:24 +02:00
|
|
|
|
// ─── Get (missing key → 404) ─────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
describe('GET /api/settings/:key', () => {
|
|
|
|
|
|
it('should return 404 for non-existent setting', async () => {
|
|
|
|
|
|
const response = await app.inject({
|
|
|
|
|
|
method: 'GET',
|
|
|
|
|
|
url: '/api/settings/non-existent-key',
|
2026-07-04 16:43:55 +02:00
|
|
|
|
headers: { authorization: `Bearer ${authToken}` },
|
2026-06-26 17:48:24 +02:00
|
|
|
|
});
|
|
|
|
|
|
expect(response.statusCode).toBe(404);
|
|
|
|
|
|
const body = JSON.parse(response.body);
|
|
|
|
|
|
expect(body.error).toContain('Setting not found');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should return a setting that was previously set', async () => {
|
|
|
|
|
|
// First set a value
|
|
|
|
|
|
await app.inject({
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
url: '/api/settings/test-key',
|
2026-07-04 16:43:55 +02:00
|
|
|
|
headers: { authorization: `Bearer ${authToken}` },
|
2026-06-26 17:48:24 +02:00
|
|
|
|
payload: { value: 'test-value' },
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const response = await app.inject({
|
|
|
|
|
|
method: 'GET',
|
|
|
|
|
|
url: '/api/settings/test-key',
|
2026-07-04 16:43:55 +02:00
|
|
|
|
headers: { authorization: `Bearer ${authToken}` },
|
2026-06-26 17:48:24 +02:00
|
|
|
|
});
|
|
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
|
|
|
|
const body = JSON.parse(response.body);
|
|
|
|
|
|
expect(body.key).toBe('test-key');
|
|
|
|
|
|
expect(body.value).toBe('test-value');
|
|
|
|
|
|
expect(body.updated_at).toBeDefined();
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Put (upsert) ───────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
describe('PUT /api/settings/:key', () => {
|
|
|
|
|
|
it('should create a new setting', async () => {
|
|
|
|
|
|
const response = await app.inject({
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
url: '/api/settings/new-setting',
|
2026-07-04 16:43:55 +02:00
|
|
|
|
headers: { authorization: `Bearer ${authToken}` },
|
2026-06-26 17:48:24 +02:00
|
|
|
|
payload: { value: 'new-value' },
|
|
|
|
|
|
});
|
|
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
|
|
|
|
const body = JSON.parse(response.body);
|
|
|
|
|
|
expect(body.key).toBe('new-setting');
|
|
|
|
|
|
expect(body.value).toBe('new-value');
|
|
|
|
|
|
expect(body.updated_at).toBeDefined();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should update an existing setting (upsert)', async () => {
|
|
|
|
|
|
// Create initial
|
|
|
|
|
|
await app.inject({
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
url: '/api/settings/upsert-key',
|
2026-07-04 16:43:55 +02:00
|
|
|
|
headers: { authorization: `Bearer ${authToken}` },
|
2026-06-26 17:48:24 +02:00
|
|
|
|
payload: { value: 'initial' },
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Update it
|
|
|
|
|
|
const response = await app.inject({
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
url: '/api/settings/upsert-key',
|
2026-07-04 16:43:55 +02:00
|
|
|
|
headers: { authorization: `Bearer ${authToken}` },
|
2026-06-26 17:48:24 +02:00
|
|
|
|
payload: { value: 'updated' },
|
|
|
|
|
|
});
|
|
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
|
|
|
|
const body = JSON.parse(response.body);
|
|
|
|
|
|
expect(body.key).toBe('upsert-key');
|
|
|
|
|
|
expect(body.value).toBe('updated');
|
|
|
|
|
|
|
|
|
|
|
|
// Verify via GET
|
|
|
|
|
|
const getRes = await app.inject({
|
|
|
|
|
|
method: 'GET',
|
|
|
|
|
|
url: '/api/settings/upsert-key',
|
2026-07-04 16:43:55 +02:00
|
|
|
|
headers: { authorization: `Bearer ${authToken}` },
|
2026-06-26 17:48:24 +02:00
|
|
|
|
});
|
|
|
|
|
|
const getBody = JSON.parse(getRes.body);
|
|
|
|
|
|
expect(getBody.value).toBe('updated');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should reject setting without value', async () => {
|
|
|
|
|
|
const response = await app.inject({
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
url: '/api/settings/no-value-key',
|
2026-07-04 16:43:55 +02:00
|
|
|
|
headers: { authorization: `Bearer ${authToken}` },
|
2026-06-26 17:48:24 +02:00
|
|
|
|
payload: {},
|
|
|
|
|
|
});
|
|
|
|
|
|
const body = JSON.parse(response.body);
|
|
|
|
|
|
expect(body.error).toContain('Value is required');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
it('should handle complex JSON values', async () => {
|
|
|
|
|
|
const complexValue = JSON.stringify({ nested: { object: true }, array: [1, 2, 3] });
|
|
|
|
|
|
const response = await app.inject({
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
url: '/api/settings/complex-config',
|
2026-07-04 16:43:55 +02:00
|
|
|
|
headers: { authorization: `Bearer ${authToken}` },
|
2026-06-26 17:48:24 +02:00
|
|
|
|
payload: { value: complexValue },
|
|
|
|
|
|
});
|
|
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
|
|
|
|
|
|
|
|
|
|
const getRes = await app.inject({
|
|
|
|
|
|
method: 'GET',
|
|
|
|
|
|
url: '/api/settings/complex-config',
|
2026-07-04 16:43:55 +02:00
|
|
|
|
headers: { authorization: `Bearer ${authToken}` },
|
2026-06-26 17:48:24 +02:00
|
|
|
|
});
|
|
|
|
|
|
const body = JSON.parse(getRes.body);
|
|
|
|
|
|
expect(body.value).toBe(complexValue);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|