FieldOps/scripts/new-request-smoke.ts
Pedro Gomes 03c15fd069 MAI CALL - step 10
Passo 10 completo. AC verificado end-to-end:

Sem foto — row criada com photoKey=null ✓
Com foto — upload para MinIO via presigned PUT + row criada com photoKey correto + conteúdo verificado via presigned GET ✓
O que foi implementado:

/maintenance/new — Client Component com: select de posto (carregado via trpc.workstation.list), input de foto com compressão canvas (max 1600px, JPEG q=0.8), preview + botão remover, textarea com contador, submit que faz upload + create + redirect
/maintenance/sent — Server Component que mostra o clientRequestId e o botão "Voltar ao início"
Build de produção limpo com 7 rotas
2026-05-16 16:26:23 +01:00

89 lines
3.4 KiB
TypeScript

/**
* AC verification for Passo 10:
* Submitting a request (with and without photo) creates the row in the DB.
* When a photo is included, the object exists in MinIO.
*/
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';
async function makeCaller(email: string) {
const user = await prisma.user.findFirst({ where: { email } });
if (!user) throw new Error(`${email} not found — run pnpm db:seed`);
const ctx = await createTRPCContext({
user: { id: user.id, email: user.email, role: user.role as 'OPERATOR', tenantId: user.tenantId },
headers: new Headers(),
});
return createCallerFactory(appRouter)(ctx);
}
async function main() {
const op1 = await makeCaller('op1@demo.local');
const workstations = await op1.workstation.list();
const ws = workstations[0];
if (!ws) throw new Error('No workstations — run pnpm db:seed');
// --- Without photo ---
console.log('1. Create request WITHOUT photo...');
const noPhoto = await op1.maintenanceRequest.create({
workstationId: ws.id,
description: 'Problema no posto — sem foto',
clientRequestId: crypto.randomUUID(),
});
if (noPhoto.status !== 'OPEN') throw new Error('Expected OPEN status');
const rowNoPhoto = await prisma.maintenanceRequest.findFirst({ where: { id: noPhoto.id } });
if (!rowNoPhoto) throw new Error('Row not found in DB');
if (rowNoPhoto.photoKey !== null) throw new Error('Expected null photoKey');
console.log(` id=${noPhoto.id} photoKey=null ✓`);
// --- With photo ---
console.log('2. Create request WITH photo...');
const photoContent = 'fake-photo-content-passo10';
const { uploadUrl, photoKey } = await op1.storage.signPhotoUpload({
contentType: 'image/jpeg',
byteSize: photoContent.length,
});
const putRes = await fetch(uploadUrl, {
method: 'PUT',
body: photoContent,
headers: { 'Content-Type': 'image/jpeg' },
});
if (!putRes.ok) throw new Error(`Photo PUT failed: ${putRes.status}`);
const withPhoto = await op1.maintenanceRequest.create({
workstationId: ws.id,
description: 'Problema no posto — com foto',
photoKey,
clientRequestId: crypto.randomUUID(),
});
if (withPhoto.status !== 'OPEN') throw new Error('Expected OPEN status');
const rowWithPhoto = await prisma.maintenanceRequest.findFirst({ where: { id: withPhoto.id } });
if (!rowWithPhoto?.photoKey) throw new Error('Expected photoKey in row');
console.log(` id=${withPhoto.id} photoKey=${rowWithPhoto.photoKey}`);
// Verify photo is in MinIO via signed GET
const { url: getUrl } = await op1.storage.signPhotoDownload({ photoKey });
const getRes = await fetch(getUrl);
if (!getRes.ok) throw new Error(`Photo GET failed: ${getRes.status}`);
const downloaded = await getRes.text();
if (downloaded !== photoContent) throw new Error('Photo content mismatch');
console.log(' Photo content in MinIO matches ✓');
await prisma.$disconnect();
console.log('\nPasso 10 AC PASSED');
}
main().catch(async (err) => {
console.error('Passo 10 AC FAILED:', err);
await prisma.$disconnect();
process.exit(1);
});