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
89 lines
3.4 KiB
TypeScript
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);
|
|
});
|