chore: initial Vector 2.0 monorepo
CI / Lint · Typecheck · Test · Build (push) Failing after 5m41s
CI / Playwright (smoke) (push) Has been skipped

Ground-up TypeScript rewrite of the Vector hardware parts inventory
system. Ships the full roadmap (Phases 0-8) in one initial commit:

- pnpm + Turbo monorepo: apps/{api,web,e2e}, packages/{db,shared,ui,config}
- Express 5 + Prisma 5 + zod validation + JWT w/ refresh-token rotation
- React 19 + Vite + shadcn/ui + TanStack Query/Table + nuqs URL state
- Repair/RMA, tags, bulk ops, saved views, CSV audit export
- Analytics dashboard on Recharts + EOL tracking
- Signed webhook subscriptions (HMAC-SHA256) with in-process emitter
- Vitest unit tests (shared schemas, api services/helpers) + Playwright skeleton
- Gitea Actions CI (lint, typecheck, test+coverage, build) + Renovate

Deferred follow-ups: Postgres cutover (data-migration script ready),
BullMQ worker for webhook delivery, @react-pdf PDF export, CSV import wizard.
This commit is contained in:
2026-04-16 20:52:32 -04:00
commit 7c0d422228
216 changed files with 19393 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
import { describe, expect, it } from 'vitest';
import { BulkPartsRequest, CreatePartRequest, PartListQuery, UpdatePartRequest } from './parts.js';
const mfgId = '11111111-1111-4111-8111-111111111111';
const binId = '22222222-2222-4222-8222-222222222222';
describe('CreatePartRequest', () => {
it('accepts a minimal valid payload', () => {
const r = CreatePartRequest.parse({
serialNumber: 'SN-1',
mpn: 'MPN-1',
manufacturerId: mfgId,
});
expect(r.serialNumber).toBe('SN-1');
});
it('rejects empty serial / mpn', () => {
expect(
CreatePartRequest.safeParse({ serialNumber: '', mpn: 'X', manufacturerId: mfgId }).success,
).toBe(false);
expect(
CreatePartRequest.safeParse({ serialNumber: 'X', mpn: '', manufacturerId: mfgId }).success,
).toBe(false);
});
it('rejects negative price', () => {
const res = CreatePartRequest.safeParse({
serialNumber: 'X',
mpn: 'Y',
manufacturerId: mfgId,
price: -1,
});
expect(res.success).toBe(false);
});
it('rejects non-uuid manufacturer id', () => {
expect(
CreatePartRequest.safeParse({ serialNumber: 'X', mpn: 'Y', manufacturerId: 'not-uuid' })
.success,
).toBe(false);
});
it('caps tagIds at 32', () => {
const tagIds = Array.from({ length: 33 }, () => '33333333-3333-4333-8333-333333333333');
expect(
CreatePartRequest.safeParse({
serialNumber: 'X',
mpn: 'Y',
manufacturerId: mfgId,
tagIds,
}).success,
).toBe(false);
});
});
describe('UpdatePartRequest', () => {
it('requires at least one field', () => {
expect(UpdatePartRequest.safeParse({}).success).toBe(false);
});
it('accepts a single field', () => {
expect(UpdatePartRequest.safeParse({ notes: 'hi' }).success).toBe(true);
});
it('permits nullable binId to clear location', () => {
const r = UpdatePartRequest.parse({ binId: null });
expect(r.binId).toBeNull();
});
});
describe('PartListQuery', () => {
it('defaults page=1, pageSize=20', () => {
const r = PartListQuery.parse({});
expect(r.page).toBe(1);
expect(r.pageSize).toBe(20);
});
it('coerces string numbers from query strings', () => {
const r = PartListQuery.parse({ page: '3', pageSize: '50' });
expect(r.page).toBe(3);
expect(r.pageSize).toBe(50);
});
it('clamps pageSize to the 100 max', () => {
expect(PartListQuery.safeParse({ pageSize: '500' }).success).toBe(false);
});
it('parses eolOnly from string and boolean', () => {
expect(PartListQuery.parse({ eolOnly: 'true' }).eolOnly).toBe(true);
expect(PartListQuery.parse({ eolOnly: 'false' }).eolOnly).toBe(false);
expect(PartListQuery.parse({ eolOnly: true }).eolOnly).toBe(true);
});
});
describe('BulkPartsRequest', () => {
it('requires at least one mutation field', () => {
expect(BulkPartsRequest.safeParse({ ids: [mfgId] }).success).toBe(false);
});
it('accepts state mutation', () => {
expect(BulkPartsRequest.safeParse({ ids: [mfgId], state: 'SPARE' }).success).toBe(true);
});
it('accepts binId=null to unassign', () => {
const r = BulkPartsRequest.parse({ ids: [mfgId], binId: null });
expect(r.binId).toBeNull();
});
it('caps ids at 500', () => {
const ids = Array.from({ length: 501 }, () => binId);
expect(BulkPartsRequest.safeParse({ ids, state: 'SPARE' }).success).toBe(false);
});
});