import { describe, expect, it } from 'vitest'; import { applyPollResult } from '../src/services/nowPlaying'; import { bearer, createUser, useTestApp } from './helpers'; const app = useTestApp(); const get = (url: string, token?: string) => app().inject({ url, headers: token ? bearer(token) : {} }); const post = (url: string, token: string, payload: object = {}) => app().inject({ method: 'POST', url, headers: bearer(token), payload }); describe('admin', () => { it('makes the first account on a server the admin, and only the first', async () => { const first = await createUser(app(), 'founder'); const second = await createUser(app(), 'second'); expect(first.user.role).toBe('admin'); expect(second.user.role).toBe('user'); }); it('keeps admin endpoints away from regular users', async () => { await createUser(app(), 'founder'); const user = await createUser(app(), 'regular'); expect((await get('/admin/stats', user.token)).statusCode).toBe(403); expect((await get('/admin/reports', user.token)).statusCode).toBe(403); expect((await get('/admin/stats')).statusCode).toBe(401); }); it('shows the report queue and resolves reports', async () => { const admin = await createUser(app(), 'founder'); const alice = await createUser(app(), 'alice'); await createUser(app(), 'spammer'); await post('/users/spammer/report', alice.token, { reason: 'spam', note: 'links everywhere' }); const queue = (await get('/admin/reports', admin.token)).json(); expect(queue).toHaveLength(1); expect(queue[0]).toMatchObject({ reason: 'spam', note: 'links everywhere', reporter: { username: 'alice' }, reported: { username: 'spammer', suspended: false } }); expect((await get('/admin/stats', admin.token)).json()).toMatchObject({ users: 3, openReports: 1 }); await post(`/admin/reports/${queue[0].id}/resolve`, admin.token); expect((await get('/admin/reports', admin.token)).json()).toHaveLength(0); expect((await get('/admin/reports?status=resolved', admin.token)).json()).toHaveLength(1); }); it('suspension signs the user out, blocks login, hides them, and stops sharing', async () => { const admin = await createUser(app(), 'founder'); const bad = await createUser(app(), 'troll'); const viewer = await createUser(app(), 'viewer'); await applyPollResult(bad.user.id, 'spotify', { track: { title: 'Loud', artist: 'X' }, isPlaying: true, progressMs: 0 }); expect((await post('/admin/users/troll/suspend', admin.token)).statusCode).toBe(200); expect((await get('/me', bad.token)).statusCode).toBe(401); const login = await app().inject({ method: 'POST', url: '/auth/login', payload: { email: 'troll@example.com', password: 'password123' } }); expect(login.statusCode).toBe(403); expect((await get('/users/troll', viewer.token)).statusCode).toBe(404); expect((await get('/users/search?q=tro', viewer.token)).json()).toEqual([]); // Admins can still look them up. expect((await get('/users/troll', admin.token)).json().nowPlaying.isPlaying).toBe(false); await app().inject({ method: 'DELETE', url: '/admin/users/troll/suspend', headers: bearer(admin.token) }); const again = await app().inject({ method: 'POST', url: '/auth/login', payload: { email: 'troll@example.com', password: 'password123' } }); expect(again.statusCode).toBe(200); }); it("can't suspend yourself or another admin", async () => { const admin = await createUser(app(), 'founder'); expect((await post('/admin/users/founder/suspend', admin.token)).statusCode).toBe(400); }); });