Remediation Plan
Testing
Backend test harness — implementation spec for plan phase 2
Feeds plan phase 2. Phase 0's uuid-crash fix has landed (crypto.randomUUID(), dep dropped); the Vitest migration below is still the durable fix (Jest itself stays a liability for other ESM-only deps). The existing test/app.e2e-spec.ts stub doesn't compile — replace it with the infra below.
1. Harness decisions
- Runner: migrate
apps/backfrom Jest to Vitest (apps/scraperalready uses it; one mock API across the repo). Mechanical:vitest.config.tsmirroring the scraper's,jest.fn→vi.fn. Do this before writing the new tests, not after. - E2E database: real Postgres via testcontainers (
pnpm --filter back add -D @testcontainers/postgresql supertest). No mocked Prisma in e2e — the authorization bugs this suite must catch live in queries. - Unit tests: mock
PrismaService(possible once Data access §1 lands) with plainvi.fn()objects, or prefer pure functions that need no mocks (Backend quality §6 extractions).
2. E2E infrastructure
import { PostgreSqlContainer, StartedPostgreSqlContainer } from '@testcontainers/postgresql';
import { execSync } from 'node:child_process';
let container: StartedPostgreSqlContainer;
export async function startTestDb(): Promise<string> {
container = await new PostgreSqlContainer('postgres:16').start();
const url = container.getConnectionUri();
execSync('pnpm --filter @marketplace/database exec prisma migrate deploy', {
env: { ...process.env, DATABASE_URL: url },
});
return url;
}
export async function stopTestDb() { await container?.stop(); }import { Test } from '@nestjs/testing';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from '../src/app.module.js';
export async function createTestApp() {
const moduleRef = await Test.createTestingModule({ imports: [AppModule] }).compile();
const app = moduleRef.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true }));
app.setGlobalPrefix('api');
await app.init();
return app;
}import request from 'supertest';
export async function registerAndLogin(app: INestApplication, email: string) {
const res = await request(app.getHttpServer())
.post('/api/auth/register')
.send({ name: 'Test', email, password: 'password123' });
return { token: res.body.accessToken as string, user: res.body.user };
}
export const auth = (token: string) => ({ Authorization: `Bearer ${token}` });Redis / Meilisearch
For the e2e scope below, neither is exercised — provide fake providers for SearchService and the BullMQ queue in the testing module override (.overrideProvider(...)) so AppModule boots without them, OR run them as additional testcontainers later. Start with overrides (simpler).
3. The first test files (in order — these lock in the security fixes)
describe('Deals authorization', () => {
it('owner can update own draft', ...) // 200
it('other user cannot PATCH someone else’s deal', ...) // expect 403
it('other user cannot publish someone else’s deal', ...) // expect 403
it('other user cannot add/delete images on someone else’s deal', ...) // 403
it('admin can update any deal', ...) // 200
it('publish only allowed from DRAFT or DECLINED', ...) // 400 from PUBLISHED
it('unauthenticated PATCH is rejected', ...) // 401
})it('participant can read discussion', ...) // 200
it('non-participant gets 403 on GET /discussions/:id', ...)
it('non-participant gets 403 on GET /messages/:discussionId', ...)
it('seller cannot start a discussion on own deal', ...) // 400it('register → login → /auth/me round-trip', ...)
it('login with wrong password → 401 with generic message', ...)
it('register with existing email → 400', ...)
it('throttler: 6th login attempt within a minute → 429', ...)
it('/auth/me response contains no password field', ...) // locks in Security §2
it('password reset token is single-use and expires', ...)
it('reset request for unknown email still returns success', ...) // anti-enumerationit('GET /deals returns { data, meta } with clamped limit (limit=10000 → 50 items max)', ...)
it('GET /deals/:id for missing id → 404', ...)4. Unit tests for the extracted pure logic
After Backend quality §6:
products.query-builder.spec.ts—buildProductWherecovers: name search, category vs categoryIds, specIds AND-semantics, price range, status/includeDrafts combinations.import-parser.spec.ts—parseImportPayload: valid payload, missing fields, malformed JSON → typed errors.pagination.spec.ts—pageArgsclamps limit to 50, floors page to 1;buildMetamath.deals.mapper.spec.ts—mapDealwith full relations and with nulls (no brand, no price).
5. Conventions
- E2E files:
apps/back/test/*.e2e-spec.ts; unit specs colocated*.spec.tsnext to the source (matches the existing scraper specs). - Each e2e file creates its own users with unique emails; DB is shared per run — tests must not assume an empty DB, only their own fixtures (or use
beforeEachtransactions if flakiness appears). - No snapshot tests on API responses (churn); assert specific fields.
- CI:
turbo run testpicks both apps up onceapps/backhas a workingtestscript (CI already runsturbo run test).