dorfteich/apps/api/src/admin/admin.e2e.db.test.ts
Claude Fable 5 1314096c94 Add account/session endpoints and typed instance settings with admin API
Server halves of #17/#18/#19: PATCH /users/me and change-password
(verifies the current password, logs out every other session),
GET/DELETE /users/me/sessions with current-session flag and protection
against revoking oneself; InstanceSettingsService as a typed, cached,
Zod-validated registry over instance_settings (schema-default fallback
for invalid stored values, audit-logged writes) consumed by the signup
flow; /admin/settings behind the new SiteAdminGuard with strict
unknown-key rejection. SessionsService moves to its own module to keep
Auth/Users acyclic. Three new e2e suites bring the api to 42 tests.

Part of #17, #18, #19

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 05:26:45 +02:00

108 lines
3.8 KiB
TypeScript

import { INestApplication } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import request from 'supertest';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { createTestApp, sessionCookieOf } from '../testing/test-app';
import { createTestPrisma, hasTestDb, uniqueSuffix } from '../testing/test-db';
import { UsersService } from '../users/users.service';
describe.skipIf(!hasTestDb)('admin settings (e2e)', () => {
let app: INestApplication;
let prisma: PrismaClient;
const suffix = uniqueSuffix();
let adminCookie: string;
let memberCookie: string;
const api = () => request(app.getHttpServer());
const password = 'ein ordentliches admin passwort';
async function makeActiveUser(name: string, siteAdmin: boolean): Promise<string> {
const users = app.get(UsersService);
const user = await users.createUser({
username: `${name}-${suffix}`,
email: `${name}-${suffix}@example.org`,
displayName: name,
password,
locale: 'en',
});
await users.markEmailVerified(user.id);
if (siteAdmin) {
await prisma.user.update({ where: { id: user.id }, data: { isSiteAdmin: true } });
}
const res = await api()
.post('/api/v1/auth/login')
.send({ usernameOrEmail: user.username, password })
.expect(200);
return sessionCookieOf(res);
}
beforeAll(async () => {
prisma = createTestPrisma();
await prisma.rateLimit.deleteMany({});
await prisma.instanceSetting.deleteMany({ where: { key: 'auth.registrationMode' } });
app = await createTestApp();
adminCookie = await makeActiveUser('root', true);
memberCookie = await makeActiveUser('member', false);
});
afterAll(async () => {
await prisma.user.deleteMany({ where: { username: { contains: suffix } } });
await prisma.mailOutbox.deleteMany({ where: { toAddress: { contains: suffix } } });
await prisma.instanceSetting.deleteMany({ where: { key: 'auth.registrationMode' } });
await prisma.$disconnect();
await app.close();
});
it('denies the settings endpoints to non-admins and anonymous callers', async () => {
await api().get('/api/v1/admin/settings').expect(401);
await api().get('/api/v1/admin/settings').set('Cookie', memberCookie).expect(403);
});
it('returns typed defaults for unset settings', async () => {
const res = await api().get('/api/v1/admin/settings').set('Cookie', adminCookie).expect(200);
expect(res.body['auth.registrationMode']).toBe('open');
expect(res.body['instance.name']).toBe('Dorfteich');
});
it('rejects invalid values with field details', async () => {
const res = await api()
.patch('/api/v1/admin/settings')
.set('Cookie', adminCookie)
.send({ 'auth.registrationMode': 'sideways' })
.expect(400);
expect(res.body.details).toHaveProperty('auth.registrationMode');
});
it('closing registration takes effect immediately, reopening too', async () => {
await api()
.patch('/api/v1/admin/settings')
.set('Cookie', adminCookie)
.send({ 'auth.registrationMode': 'closed' })
.expect(200);
const newcomer = {
username: `late-${suffix}`,
email: `late-${suffix}@example.org`,
displayName: 'Late',
password: 'auch ein gutes passwort',
};
await api().post('/api/v1/auth/signup').send(newcomer).expect(403);
await api()
.patch('/api/v1/admin/settings')
.set('Cookie', adminCookie)
.send({ 'auth.registrationMode': 'open' })
.expect(200);
await api().post('/api/v1/auth/signup').send(newcomer).expect(201);
});
it('rejects unknown setting keys', async () => {
await api()
.patch('/api/v1/admin/settings')
.set('Cookie', adminCookie)
.send({ 'made.up': true })
.expect(400);
});
});