mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
724c88c201
A mechanical prettier --write over server/src, server/tests, server/scripts and shared/src with the workspaces' .prettierrc; nothing but layout and import order changes, and every file keeps the same set of imports. shared is formatted throughout and its format baseline is empty. The server baseline keeps the 35 files that Prettier would grow past their lint:size limit (they need a split first, which is not a formatting change), and the generated entity files stay as their generator writes them.
110 lines
4.7 KiB
TypeScript
110 lines
4.7 KiB
TypeScript
/**
|
|
* /api/admin/google-quota e2e (#1582): the real JwtAuthGuard, AdminGuard and
|
|
* Zod pipe against the migrated snapshot db. Admin only, a validated body, and the
|
|
* status reflecting what was stored.
|
|
*/
|
|
import { db } from '../../src/db/database';
|
|
import { AppSettings } from '../../src/db/entities/AppSettings.entity';
|
|
import { GoogleApiUsage } from '../../src/db/entities/GoogleApiUsage.entity';
|
|
import { AuditService } from '../../src/nest/audit/audit.service';
|
|
import { TrekExceptionFilter } from '../../src/nest/common/trek-exception.filter';
|
|
import { ZodValidationPipe } from '../../src/nest/common/zod-validation.pipe';
|
|
import { GoogleQuotaModule } from '../../src/nest/google-quota/google-quota.module';
|
|
import { deleteRows, upsertRow } from '../helpers/factories/rows';
|
|
import { makeAdmin, makeUser } from '../helpers/factories/users';
|
|
import { createTestMikroOrmModule, createTestOrm, type TestOrm } from '../helpers/test-orm';
|
|
import { TestUnitOfWorkModule } from '../helpers/test-uow';
|
|
import { sessionCookie } from './harness';
|
|
import { Test } from '@nestjs/testing';
|
|
import { todayUtc } from '@trek/shared';
|
|
|
|
import cookieParser from 'cookie-parser';
|
|
import type { Server } from 'http';
|
|
import request from 'supertest';
|
|
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
|
|
|
|
vi.mock('../../src/db/database', async () => {
|
|
const { createSnapshotTestDb, buildDbMock } = await import('../helpers/db-mock');
|
|
return buildDbMock(createSnapshotTestDb());
|
|
});
|
|
|
|
let orm: TestOrm;
|
|
|
|
const USER = 1;
|
|
const ADMIN = 2;
|
|
|
|
describe('/api/admin/google-quota e2e (real guards + temp SQLite)', () => {
|
|
let server: Server;
|
|
let app: Awaited<ReturnType<typeof build>>;
|
|
const writeAudit = vi.fn();
|
|
|
|
async function build() {
|
|
const moduleRef = await Test.createTestingModule({
|
|
imports: [await TestUnitOfWorkModule.forRoot(db), await createTestMikroOrmModule(db), GoogleQuotaModule],
|
|
})
|
|
.overrideProvider(AuditService)
|
|
.useValue({ writeAudit })
|
|
.compile();
|
|
const nest = moduleRef.createNestApplication();
|
|
nest.use(cookieParser());
|
|
nest.useGlobalFilters(new TrekExceptionFilter());
|
|
// Mirror the production APP_PIPE (app.module.ts): DTO-typed bodies validate by metatype.
|
|
nest.useGlobalPipes(new ZodValidationPipe());
|
|
await nest.init();
|
|
return nest;
|
|
}
|
|
|
|
beforeAll(async () => {
|
|
orm = await createTestOrm(db);
|
|
// Pinned ids: sessionCookie(USER) and (ADMIN) sign for exactly these users.
|
|
await makeUser(orm, { id: USER, username: 'e2e-user', email: 'e2e@example.test' });
|
|
await makeAdmin(orm, { id: ADMIN, username: 'e2e-admin', email: 'admin@example.com' });
|
|
await deleteRows(orm, AppSettings, { key: 'google_daily_limit' });
|
|
app = await build();
|
|
server = app.getHttpServer();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await app.close();
|
|
await orm.close();
|
|
});
|
|
|
|
it('GQUOTA-E2E-001: 401 without a cookie and 403 for a non-admin', async () => {
|
|
expect((await request(server).get('/api/admin/google-quota')).status).toBe(401);
|
|
expect((await request(server).get('/api/admin/google-quota').set('Cookie', sessionCookie(USER))).status).toBe(403);
|
|
expect(
|
|
(await request(server).put('/api/admin/google-quota').set('Cookie', sessionCookie(USER)).send({ daily_limit: 5 }))
|
|
.status,
|
|
).toBe(403);
|
|
});
|
|
|
|
it('GQUOTA-E2E-002: stores the ceiling, reports today, and audits the change', async () => {
|
|
await upsertRow(orm, GoogleApiUsage, { day: todayUtc(), calls: 7 });
|
|
const put = await request(server)
|
|
.put('/api/admin/google-quota')
|
|
.set('Cookie', sessionCookie(ADMIN))
|
|
.send({ daily_limit: 5 });
|
|
expect(put.status).toBe(200);
|
|
expect(put.body).toEqual({ daily_limit: 5, used_today: 7, exhausted: true });
|
|
expect(writeAudit).toHaveBeenCalledWith(
|
|
expect.objectContaining({ action: 'admin.google_daily_limit', details: { daily_limit: 5 } }),
|
|
);
|
|
|
|
const get = await request(server).get('/api/admin/google-quota').set('Cookie', sessionCookie(ADMIN));
|
|
expect(get.body).toEqual({ daily_limit: 5, used_today: 7, exhausted: true });
|
|
|
|
const off = await request(server)
|
|
.put('/api/admin/google-quota')
|
|
.set('Cookie', sessionCookie(ADMIN))
|
|
.send({ daily_limit: null });
|
|
expect(off.body).toEqual({ daily_limit: null, used_today: 7, exhausted: false });
|
|
});
|
|
|
|
it('GQUOTA-E2E-003: 400 from the pipe for a body that is not a whole, non-negative number or null', async () => {
|
|
for (const bad of [{ daily_limit: -1 }, { daily_limit: 1.5 }, { daily_limit: 'many' }, {}]) {
|
|
const res = await request(server).put('/api/admin/google-quota').set('Cookie', sessionCookie(ADMIN)).send(bad);
|
|
expect(res.status, JSON.stringify(bad)).toBe(400);
|
|
}
|
|
});
|
|
});
|