MY QUALITY - First iteration
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* 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());
|
||||
Reference in New Issue
Block a user