/** * MY QUALITY smoke test — exercises the operatorSession + qualityDefect * procedures end-to-end via tRPC callers (same pattern as report-smoke.ts): * QCP raises a defect -> the badged-in operator sees it -> acknowledges -> * corrects. Plus role guards and state-machine conflicts. * * Run: pnpm tsx scripts/quality-smoke.ts * Requires: Docker Postgres running + pnpm db:seed already done. * NOTE: this creates a defect and starts/ends op2's session; re-run db:seed * afterwards for a pristine demo state. */ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { config as loadEnv } from 'dotenv'; const here = path.dirname(fileURLToPath(import.meta.url)); loadEnv({ path: path.resolve(here, '../.env') }); import { prisma } from '../packages/db/src/index.js'; import { appRouter, createTRPCContext } from '../packages/api/src/index.js'; import { createCallerFactory } from '../packages/api/src/trpc.js'; let passed = 0; let failed = 0; function ok(label: string) { console.log(` ✓ ${label}`); passed++; } function fail(label: string, detail?: string) { console.error(` ✗ ${label}${detail ? ` — ${detail}` : ''}`); failed++; } function assert(condition: boolean, label: string, detail?: string) { if (condition) ok(label); else fail(label, detail); } function codeOf(err: unknown): string | undefined { return (err as { code?: string }).code; } async function makeCaller(email: string) { const user = await prisma.user.findFirst({ where: { email } }); if (!user) throw new Error(`User ${email} not found — run pnpm db:seed`); const ctx = await createTRPCContext({ user: { id: user.id, email: user.email, role: user.role as 'ADMIN' | 'SUPERVISOR' | 'QUALITY' | 'OPERATOR', tenantId: user.tenantId, }, headers: new Headers(), }); return createCallerFactory(appRouter)(ctx); } async function main() { console.log('\nMY QUALITY smoke — running assertions against the real procedures…\n'); const qcp = await makeCaller('qcp@demo.local'); const op1 = await makeCaller('op1@demo.local'); const op2 = await makeCaller('op2@demo.local'); // 1. op1 has a seeded active session at CTR04 const session = await op1.operatorSession.current(); assert(!!session, 'op1 has an active session'); assert(session?.workstation.code === 'CTR04', `op1 session at CTR04 (got ${session?.workstation.code})`); const workstationId = session!.workstationId; // 2. QCP raises a defect at op1's workstation const created = await qcp.qualityDefect.create({ workstationId, defectType: 'Smoke — aperto', location: 'Banco traseiro', description: 'Defeito de teste criado pelo smoke.', rfsCode: 'RFS-SMOKE', }); assert(created.status === 'OPEN', `created defect is OPEN (got ${created.status})`); assert(created.workstation.code === 'CTR04', 'created defect at CTR04'); const defectId = created.id; // 3. op1 sees it in forMyStation const mine = await op1.qualityDefect.forMyStation(); assert(mine.some((d) => d.id === defectId), 'op1 sees the new defect at their station'); // 4. op2 (no session at CTR04) does NOT see it const op2current = await op2.operatorSession.current(); if (!op2current) { const op2defects = await op2.qualityDefect.forMyStation(); assert(op2defects.length === 0, 'op2 (not badged in) sees no defects'); } else { ok('op2 already had a session (skipping no-session check)'); } // 5. op1 acknowledges (OPEN -> ACKNOWLEDGED) const ack = await op1.qualityDefect.acknowledge({ id: defectId }); assert(ack.status === 'ACKNOWLEDGED', `acknowledge -> ACKNOWLEDGED (got ${ack.status})`); assert(ack.acknowledgedBy?.email === 'op1@demo.local', 'acknowledgedBy is op1'); // 6. acknowledge again -> CONFLICT try { await op1.qualityDefect.acknowledge({ id: defectId }); fail('re-acknowledge -> CONFLICT', 'no error thrown'); } catch (err) { assert(codeOf(err) === 'CONFLICT', 're-acknowledge -> CONFLICT', `got ${codeOf(err)}`); } // 7. op1 corrects (ACKNOWLEDGED -> CORRECTED) const corrected = await op1.qualityDefect.correct({ id: defectId, correctionNote: 'Corrigido no smoke.' }); assert(corrected.status === 'CORRECTED', `correct -> CORRECTED (got ${corrected.status})`); assert(corrected.correctedBy?.email === 'op1@demo.local', 'correctedBy is op1'); // 8. correct again -> CONFLICT try { await op1.qualityDefect.correct({ id: defectId }); fail('re-correct -> CONFLICT', 'no error thrown'); } catch (err) { assert(codeOf(err) === 'CONFLICT', 're-correct -> CONFLICT', `got ${codeOf(err)}`); } // 9. corrected defect drops out of the operator's default forMyStation (OPEN+ACK) const mineAfter = await op1.qualityDefect.forMyStation(); assert(!mineAfter.some((d) => d.id === defectId), 'CORRECTED defect no longer in forMyStation default'); // 10. operator may NOT create a defect (requireRole QUALITY/ADMIN) try { await op1.qualityDefect.create({ workstationId, defectType: 'x', description: 'nope nope' }); fail('operator create -> FORBIDDEN', 'no error thrown'); } catch (err) { assert(codeOf(err) === 'FORBIDDEN', 'operator create -> FORBIDDEN', `got ${codeOf(err)}`); } // 11. QCP queue includes the defect we created const queue = await qcp.qualityDefect.queue({ statuses: ['CORRECTED'] }); assert(queue.some((d) => d.id === defectId), 'QCP queue lists the corrected defect'); // 12. operatorSession start/end roundtrip on op2 const startedAt2 = await op2.operatorSession.start({ workstationId }); assert(startedAt2.workstationId === workstationId, 'op2 badge-in starts a session'); const op2now = await op2.operatorSession.current(); assert(op2now?.id === startedAt2.id, 'op2 current() returns the started session'); await op2.operatorSession.end(); const op2ended = await op2.operatorSession.current(); assert(op2ended === null, 'op2 badge-out clears the active session'); console.log(`\n${passed} passed, ${failed} failed.\n`); if (failed > 0) process.exit(1); } main() .catch((err) => { console.error('Quality smoke failed:', err); process.exit(1); }) .finally(() => prisma.$disconnect());