chore(autonomous-session): consolidate uncommitted work from prior session

Bundles the prior autonomous-session output that was sitting unstaged:

- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
  never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
  after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
  redirects (ocr to ai, reports to dashboard, invitations to users),
  docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
  flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
  let-reassign), set-state-in-effect disables in CountryFlag and
  UploadForSigning preview-bytes effect, unused 'confirm' destructures in
  interest contract + reservation tabs, unescaped apostrophe in test-template
  card copy
This commit is contained in:
2026-05-23 00:52:59 +02:00
parent 43719b49e9
commit 221ae5784e
749 changed files with 7440 additions and 3118 deletions

View File

@@ -160,7 +160,7 @@ describe('ai-budget service', () => {
it('periodStart honors day/week/month boundaries (UTC)', () => {
const wed = new Date(Date.UTC(2026, 3, 29, 14, 30)); // Wed 2026-04-29 14:30 UTC
expect(periodStart('day', wed).toISOString()).toBe('2026-04-29T00:00:00.000Z');
// 2026-04-27 was a Monday week starts there.
// 2026-04-27 was a Monday - week starts there.
expect(periodStart('week', wed).toISOString()).toBe('2026-04-27T00:00:00.000Z');
expect(periodStart('month', wed).toISOString()).toBe('2026-04-01T00:00:00.000Z');
});

View File

@@ -1,5 +1,5 @@
/**
* Engine integration test drives `runAlertEngineForPorts` against
* Engine integration test - drives `runAlertEngineForPorts` against
* seeded conditions and asserts: (1) correct alerts upsert, (2) running
* twice doesn't duplicate, (3) mutating state auto-resolves stale alerts.
*
@@ -129,7 +129,7 @@ describe('alert engine', () => {
await runAlertEngineForPorts([port.id]);
expect(await listOpenAlerts(port.id, 'reservation.no_agreement')).toHaveLength(1);
// Add an agreement document condition no longer fires.
// Add an agreement document - condition no longer fires.
await db.insert(documents).values({
portId: port.id,
reservationId: resv!.id,
@@ -200,7 +200,7 @@ describe('alert engine', () => {
const summary = await runAlertEngineForPorts([port.id]);
expect(summary.portsScanned).toBe(1);
expect(summary.rulesEvaluated).toBeGreaterThan(0);
// No conditions seeded no rules should fail.
// No conditions seeded - no rules should fail.
expect(summary.errors).toHaveLength(0);
});
});

View File

@@ -39,7 +39,7 @@ async function makeAlert(portId: string) {
return row.id;
}
describe('alerts service tenant isolation', () => {
describe('alerts service - tenant isolation', () => {
it('dismissAlert is a no-op when called with the wrong portId', async () => {
const portA = await makePort();
const portB = await makePort();

View File

@@ -1,5 +1,5 @@
/**
* Analytics service integration tests exercise the four computations
* Analytics service integration tests - exercise the four computations
* against a seeded port + assert the cache layer reads/writes correctly.
*/
@@ -67,7 +67,7 @@ describe('analytics service', () => {
it('returns 7 points for 7d range with cumulative won-deal occupancy', async () => {
// Post 2026-05-14 the timeline derives occupancy from won
// interests (cumulative as of each day) rather than active
// reservations see analytics.service.ts comment + PRE-DEPLOY-
// reservations - see analytics.service.ts comment + PRE-DEPLOY-
// PLAN § 1.1.3. Fixture: 3 berths, one of which sold 5 days ago.
const port = await makePort();
await makeBerth({ portId: port.id });

View File

@@ -1,5 +1,5 @@
/**
* Port-scoped global reservations list locks in feat(marina): the new
* Port-scoped global reservations list - locks in feat(marina): the new
* `GET /api/v1/berth-reservations` endpoint that powers the
* `[portSlug]/berth-reservations` page. The route is thin (parseQuery →
* listReservations); the test guarantees port scoping at the handler

View File

@@ -36,7 +36,7 @@ describe('POST /api/v1/companies', () => {
const res1 = await createHandler(req1, ctx, {});
expect(res1.status).toBe(201);
// Same name with different case should still conflict.
// Same name with different case - should still conflict.
const req2 = makeMockRequest('POST', 'http://localhost/api/v1/companies', {
body: { name: name.toUpperCase() },
});

View File

@@ -94,7 +94,7 @@ describe('GET /api/v1/companies/[id]/members', () => {
);
expect(delRes.status).toBe(204);
// Default active only.
// Default - active only.
const activeOnlyRes = await listHandler(
makeMockRequest('GET', `http://localhost/api/v1/companies/${company.id}/members`),
ctx,
@@ -375,7 +375,7 @@ describe('POST /api/v1/companies/[id]/members/[mid]/set-primary', () => {
);
const m1 = ((await m1Res.json()) as any).data;
// M2, M3 not primary.
// M2, M3 - not primary.
const m2Res = await createHandler(
makeMockRequest('POST', `http://localhost/api/v1/companies/${company.id}/members`, {
body: {

View File

@@ -416,7 +416,7 @@ describe('PATCH /api/v1/berth-reservations/[id]', () => {
it('returns 403 when caller lacks reservations.cancel for cancel action', async () => {
const { port, reservation } = await seedReservation();
// Sales agent has activate but NOT cancel.
// Sales agent - has activate but NOT cancel.
const ctx = makeMockCtx({
portId: port.id,
permissions: makeSalesAgentPermissions(),

View File

@@ -1,5 +1,5 @@
/**
* Saved-views ownership enforcement locks in the 403/404 split shipped
* Saved-views ownership enforcement - locks in the 403/404 split shipped
* in fix(auth). The route handlers preflight `assertViewOwner` BEFORE the
* service call, so even if the service's internal userId filter is later
* refactored, the route still rejects cross-user mutations.
@@ -96,7 +96,7 @@ describe('saved-views ownership enforcement', () => {
it('PATCH on a view in a different port: 404 (cross-port enumeration is blocked)', async () => {
// The view exists in `portId` but the auth context says we're operating
// in a different port. The lookup is scoped to `(id, portId)` so the row
// is invisible should 404, not 403.
// is invisible - should 404, not 403.
const otherPort = await makePort();
const ctx = makeMockCtx({ portId: otherPort.id, userId: ownerUserId });
const res = await patchHandler(

View File

@@ -94,7 +94,7 @@ describe('PATCH /api/v1/yachts/[id]', () => {
// Validator strips owner fields, so we need to bypass it to reach the service's defensive guard.
// Test the service layer defense by calling the handler with a payload that the validator
// would accept but which also contains an unknown field that matches the forbidden keys.
// Actually the validator just omits `owner` additional keys `currentOwnerId` etc. pass
// Actually the validator just omits `owner` - additional keys `currentOwnerId` etc. pass
// through Zod's .partial() (which still omits unknown keys by default).
// Zod .strip() is default, so unknown keys are dropped: we assert on the service directly.
const { updateYacht } = await import('@/lib/services/yachts.service');
@@ -264,7 +264,7 @@ describe('GET /api/v1/yachts/[id]/ownership-history', () => {
expect(res.status).toBe(200);
const body = (await res.json()) as any;
expect(body.data).toHaveLength(2);
// Sorted DESC by startDate newest first
// Sorted DESC by startDate - newest first
const firstStart = new Date(body.data[0].startDate).getTime();
const secondStart = new Date(body.data[1].startDate).getTime();
expect(firstStart).toBeGreaterThanOrEqual(secondStart);

View File

@@ -82,7 +82,7 @@ describe('GET /api/v1/yachts (listHandler)', () => {
});
});
describe('POST /api/v1/yachts permission gate', () => {
describe('POST /api/v1/yachts - permission gate', () => {
it('viewer (no yachts.create) receives 403 through full pipeline', async () => {
const port = await makePort();
const client = await makeClient({ portId: port.id });

View File

@@ -1,5 +1,5 @@
/**
* PR10 audit log search.
* PR10 - audit log search.
*
* Validates:
* 1. Tsvector full-text search via the GENERATED `search_text` column

View File

@@ -1,12 +1,12 @@
/**
* Task 11 backfill-document-folders integration tests.
* Task 11 - backfill-document-folders integration tests.
*
* Five cases:
* 1. Creates system roots and entity subfolders.
* 2. Sets files.folder_id from entity FKs.
* 3. Copies entity FKs from completed workflows onto signed files.
* 4. Idempotent second run produces the same result.
* 5. Port isolation does not touch other ports.
* 4. Idempotent - second run produces the same result.
* 5. Port isolation - does not touch other ports.
*/
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
@@ -125,7 +125,7 @@ describe('backfill-document-folders · runBackfill', () => {
.insert(files)
.values({
portId,
// No clientId set simulates legacy completion before entity FK auto-propagation.
// No clientId set - simulates legacy completion before entity FK auto-propagation.
filename: 'signed-eoi.pdf',
originalName: 'signed-eoi.pdf',
storagePath: `${portId}/signed-eoi.pdf`,
@@ -161,7 +161,7 @@ describe('backfill-document-folders · runBackfill', () => {
// ── Test 4: Idempotent ────────────────────────────────────────────────────────
it('is idempotent running twice produces the same number of folder rows', async () => {
it('is idempotent - running twice produces the same number of folder rows', async () => {
const client = await makeClient({ portId });
await db.insert(files).values({
@@ -217,7 +217,7 @@ describe('backfill-document-folders · runBackfill', () => {
.from(documentFolders)
.where(eq(documentFolders.portId, otherPort.id));
// The other port should have zero folders the backfill was not run for it.
// The other port should have zero folders - the backfill was not run for it.
expect(otherPortFolders).toHaveLength(0);
});
});

View File

@@ -43,7 +43,7 @@ beforeEach(async () => {
});
afterEach(async () => {
// No file cleanup needed the filesystem backend writes to a tmp root.
// No file cleanup needed - the filesystem backend writes to a tmp root.
});
function fakePdf(): Buffer {
@@ -240,7 +240,7 @@ describe('applyParseResults', () => {
lengthFt: 200,
bowFacing: 'East',
// unknown / non-allowlisted column should be silently dropped:
// @ts-expect-error testing the allowlist
// @ts-expect-error - testing the allowlist
hackThePlanet: 'pwn',
},
port.id,
@@ -323,7 +323,7 @@ describe('cross-port tenant guard', () => {
});
// Port B caller passing port A's berth id must hit NotFoundError on
// every entrypoint including read-only listing, which previously
// every entrypoint - including read-only listing, which previously
// returned 15-min presigned download URLs to the foreign port's PDFs.
await expect(listBerthPdfVersions(berthA.id, portB.id)).rejects.toThrow(/berth/i);
await expect(rollbackToVersion(berthA.id, v1.versionId, portB.id)).rejects.toThrow(/berth/i);

View File

@@ -1,5 +1,5 @@
/**
* clients.service.createRelationship tenant-FK validation tests.
* clients.service.createRelationship - tenant-FK validation tests.
*
* Covers the fix that requires both clientAId (the URL id) and clientBId
* (the body id) to belong to the caller's port. The list endpoint joins
@@ -8,7 +8,7 @@
*/
import { describe, it, expect, beforeAll } from 'vitest';
describe('clients.service createRelationship port isolation', () => {
describe('clients.service - createRelationship port isolation', () => {
let createRelationship: typeof import('@/lib/services/clients.service').createRelationship;
let makePort: typeof import('../helpers/factories').makePort;

View File

@@ -10,7 +10,7 @@ import { describe, it, expect } from 'vitest';
import { createCrmInvite } from '@/lib/services/crm-invite.service';
import { ValidationError } from '@/lib/errors';
describe('createCrmInvite super-admin gate', () => {
describe('createCrmInvite - super-admin gate', () => {
it('rejects super-admin invites when caller is not a super-admin', async () => {
await expect(
createCrmInvite({

View File

@@ -35,7 +35,7 @@ beforeAll(async () => {
await sql.end();
dbAvailable = true;
} catch {
console.warn('[crud-audit] Test database not available skipping integration tests');
console.warn('[crud-audit] Test database not available - skipping integration tests');
}
});
@@ -99,7 +99,7 @@ async function getAuditEntries(
// ─── Client Audit Tests ───────────────────────────────────────────────────────
describe('CRUD Audit Clients', () => {
describe('CRUD Audit - Clients', () => {
let portId: string;
beforeAll(async () => {
@@ -200,7 +200,7 @@ describe('CRUD Audit — Clients', () => {
// ─── Interest Audit Tests ─────────────────────────────────────────────────────
describe('CRUD Audit Interests', () => {
describe('CRUD Audit - Interests', () => {
let portId: string;
let clientId: string;
@@ -291,7 +291,7 @@ describe('CRUD Audit — Interests', () => {
// ─── Berth Audit Tests ────────────────────────────────────────────────────────
describe('CRUD Audit Berths', () => {
describe('CRUD Audit - Berths', () => {
let portId: string;
let berthId: string;

View File

@@ -32,7 +32,7 @@ beforeAll(async () => {
await sql.end();
dbAvailable = true;
} catch {
console.warn('[custom-fields] Test database not available skipping integration tests');
console.warn('[custom-fields] Test database not available - skipping integration tests');
}
});
@@ -66,7 +66,7 @@ async function cleanupPort(portId: string): Promise<void> {
// ─── Definitions Tests ────────────────────────────────────────────────────────
describe('Custom Fields Definitions', () => {
describe('Custom Fields - Definitions', () => {
let portId: string;
const userId = crypto.randomUUID();
@@ -160,7 +160,7 @@ describe('Custom Fields — Definitions', () => {
meta,
);
// Cast bypasses TS the service should guard against this at runtime.
// Cast bypasses TS - the service should guard against this at runtime.
await expect(
updateDefinition(
portId,
@@ -205,7 +205,7 @@ describe('Custom Fields — Definitions', () => {
// ─── Values Tests ─────────────────────────────────────────────────────────────
describe('Custom Fields Values', () => {
describe('Custom Fields - Values', () => {
let portId: string;
const userId = crypto.randomUUID();
const entityId = crypto.randomUUID();
@@ -246,7 +246,7 @@ describe('Custom Fields — Values', () => {
expect(entry).toBeDefined();
expect(entry!.value).not.toBeNull();
// value is stored as jsonb the raw stored value
// value is stored as jsonb - the raw stored value
expect((entry!.value as Record<string, unknown>).value).toBe('GOLD-2024');
});
@@ -321,10 +321,10 @@ describe('Custom Fields — Values', () => {
// The previous suite seeded ONE port and verified CRUD inside it. The audit
// (HIGH §20 / auditor-J Issue 3) flagged that the suite never asserted that
// a definition created in port A is invisible from port B, nor that
// setValues refuses cross-port writes combined with the deferred
// setValues refuses cross-port writes - combined with the deferred
// custom-fields-hardcoded-clients gap, no test would catch a regression.
describe('Custom Fields Cross-port Isolation', () => {
describe('Custom Fields - Cross-port Isolation', () => {
let portA: string;
let portB: string;
const userId = crypto.randomUUID();
@@ -410,7 +410,7 @@ describe('Custom Fields — Cross-port Isolation', () => {
);
// Caller in port B tries to write a value keyed to port A's field id.
// The service must refuse either by throwing, or by no-oping
// The service must refuse - either by throwing, or by no-oping
// (returning without touching port A's data). Either way port A's
// value-store for the entity must remain unchanged.
let threw = false;

View File

@@ -1,5 +1,5 @@
/**
* Client merge service end-to-end integration test.
* Client merge service - end-to-end integration test.
*
* Spins up two real clients in a real port via the factory helpers,
* attaches a few satellites (interest, contact, address, note),

View File

@@ -1,5 +1,5 @@
/**
* Match-candidates API integration test.
* Match-candidates API - integration test.
*
* Exercises the GET /api/v1/clients/match-candidates handler against a
* real port + clients pool. Verifies the dedup library's at-create
@@ -107,7 +107,7 @@ describe('GET /api/v1/clients/match-candidates', () => {
});
it('returns medium-confidence partial matches', async () => {
// Same name, different contact info Pattern F territory.
// Same name, different contact info - Pattern F territory.
const port = await makePort();
const ctx = makeMockCtx({ portId: port.id });
const existing = await makeClient({
@@ -127,7 +127,7 @@ describe('GET /api/v1/clients/match-candidates', () => {
name: 'Etiennette Clamouze',
});
// Either no match (low confidence filtered out) or a medium one
// Either no match (low confidence filtered out) or a medium one -
// either is fine. Critically, NOT high.
if (data.length > 0) {
expect(data[0]!.confidence).not.toBe('high');

View File

@@ -1,8 +1,8 @@
/**
* Task 3 document-folders service: listTree + createFolder (TDD).
* Task 4 renameFolder + moveFolder (TDD).
* Task 3 - document-folders service: listTree + createFolder (TDD).
* Task 4 - renameFolder + moveFolder (TDD).
*
* Uses the makePort factory (not a "setupTestPort" helper that name
* Uses the makePort factory (not a "setupTestPort" helper - that name
* doesn't exist in this codebase). TEST_USER_ID is resolved once via
* beforeAll from any seeded user, matching the pattern in
* alerts-tenant-isolation.test.ts and gdpr-export.test.ts.

View File

@@ -112,9 +112,9 @@ describe('MERGE_FIELDS catalog', () => {
});
});
// ─── resolveTemplate EOI scope tokens ───────────────────────────────────────
// ─── resolveTemplate - EOI scope tokens ───────────────────────────────────────
describe('resolveTemplate EOI scope tokens', () => {
describe('resolveTemplate - EOI scope tokens', () => {
const EOI_TEMPLATE_BODY = [
'Client: {{client.fullName}} / {{client.email}} / {{client.phone}}',
'Yacht: {{yacht.name}} HN={{yacht.hullNumber}} LenFt={{yacht.lengthFt}} LenM={{yacht.lengthM}} YB={{yacht.yearBuilt}}',
@@ -273,7 +273,7 @@ describe('resolveTemplate — EOI scope tokens', () => {
});
});
describe('resolveTemplate company-owned yacht', () => {
describe('resolveTemplate - company-owned yacht', () => {
it('populates company.* tokens and owner.legalName for company-owned yachts', async () => {
const port = await makePort();
const company = await makeCompany({
@@ -354,9 +354,9 @@ describe('resolveTemplate — company-owned yacht', () => {
});
});
// ─── resolveTemplate legacy fallback path ───────────────────────────────────
// ─── resolveTemplate - legacy fallback path ───────────────────────────────────
describe('resolveTemplate legacy fallback (no interestId)', () => {
describe('resolveTemplate - legacy fallback (no interestId)', () => {
it('falls back to direct client lookup when no interestId is provided', async () => {
const port = await makePort();
const client = await makeClient({

View File

@@ -189,7 +189,7 @@ const meta = {
// ─── Pathway: inapp ───────────────────────────────────────────────────────────
describe('generateAndSign inapp pathway', () => {
describe('generateAndSign - inapp pathway', () => {
it('generates PDF via pdfme, uploads to MinIO, and sends to Documenso', async () => {
const client = await import('@/lib/services/documenso-client');
vi.mocked(client.createDocument).mockResolvedValue({
@@ -350,7 +350,7 @@ describe('generateAndSign — inapp pathway', () => {
// ─── Pathway: documenso-template ──────────────────────────────────────────────
describe('generateAndSign documenso-template pathway', () => {
describe('generateAndSign - documenso-template pathway', () => {
it('calls Documenso template-generate endpoint and records a documents row', async () => {
const client = await import('@/lib/services/documenso-client');
vi.mocked(client.generateDocumentFromTemplate).mockResolvedValue({

View File

@@ -1,5 +1,5 @@
/**
* Task 7 handleDocumentCompleted auto-deposit.
* Task 7 - handleDocumentCompleted auto-deposit.
*
* Verifies that when a document is completed:
* - The signed PDF is deposited into the owner's entity subfolder
@@ -25,7 +25,7 @@ import { handleDocumentCompleted } from '@/lib/services/documents.service';
import { ensureSystemRoots } from '@/lib/services/document-folders.service';
import { makeClient, makeCompany, makePort, makeYacht } from '../helpers/factories';
// Stub Documenso download do NOT hit the network.
// Stub Documenso download - do NOT hit the network.
vi.mock('@/lib/services/documenso-client', async (importOriginal) => {
const real = await importOriginal<typeof import('@/lib/services/documenso-client')>();
return {
@@ -34,7 +34,7 @@ vi.mock('@/lib/services/documenso-client', async (importOriginal) => {
};
});
// Stub storage backend write to an in-memory map so no MinIO required.
// Stub storage backend - write to an in-memory map so no MinIO required.
const stubPuts = new Map<string, Buffer>();
vi.mock('@/lib/storage', async (importOriginal) => {
const real = await importOriginal<typeof import('@/lib/storage')>();
@@ -171,7 +171,7 @@ describe('handleDocumentCompleted · auto-deposit', () => {
.values({
portId,
interestId: interest!.id,
// All direct owner FKs null owner must be resolved via interest.
// All direct owner FKs null - owner must be resolved via interest.
documentType: 'eoi',
title: 'Auto-deposit test EOI (via interest)',
status: 'partially_signed',

View File

@@ -1,5 +1,5 @@
/**
* DOCUMENT_EXPIRED webhook handling locks in fix(documenso). The handler
* DOCUMENT_EXPIRED webhook handling - locks in fix(documenso). The handler
* was previously defined but never wired to the route's event switch, so
* expired EOIs stayed in `sent` / `partially_signed` forever.
*/
@@ -88,7 +88,7 @@ describe('handleDocumentExpired', () => {
});
it('is a no-op when the documensoId does not match any document', async () => {
// Should NOT throw the handler logs a warning and returns. Verify no
// Should NOT throw - the handler logs a warning and returns. Verify no
// exception propagates up to the webhook route.
await expect(
handleDocumentExpired({ documentId: 'definitely-not-a-real-doc' }),
@@ -98,7 +98,7 @@ describe('handleDocumentExpired', () => {
it('does not flip a document in port B when port A receives the expired event', async () => {
// Two ports holding the same documenso_id (legacy data, or a future
// Documenso-instance migration that reuses ids). The handler currently
// mutates whichever document `findFirst` returns locking in the
// mutates whichever document `findFirst` returns - locking in the
// intended behaviour now means a future port_id-aware handler can
// be added without regressing this guard.
//
@@ -140,7 +140,7 @@ describe('handleDocumentExpired', () => {
// resolve this from the secret → port mapping in the deferred fix).
await handleDocumentExpired({ documentId: sharedDocumensoId, portId: portA.id });
// Port-A doc flipped, port-B unchanged. Pre-fix, both flip this
// Port-A doc flipped, port-B unchanged. Pre-fix, both flip - this
// assertion locks the boundary in once the handler scope lands.
const afterA = await db.query.documents.findFirst({ where: eq(documents.id, docA!.id) });
const afterB = await db.query.documents.findFirst({ where: eq(documents.id, docB!.id) });

View File

@@ -1,5 +1,5 @@
/**
* PR6 documents hub `eoi_queue` tab.
* PR6 - documents hub `eoi_queue` tab.
*
* Verifies that:
* - `listDocuments` with tab='eoi_queue' returns only EOI docs in
@@ -8,7 +8,7 @@
* - Completed/expired EOIs are excluded (those belong to other tabs)
*
* (Note: `getHubTabCounts` and the /hub-counts route were removed when the
* hub rebuild dropped the count-strip KPI surface the count assertions
* hub rebuild dropped the count-strip KPI surface - the count assertions
* that used to live here went with them.)
*/
@@ -19,7 +19,7 @@ import { documents } from '@/lib/db/schema/documents';
import { listDocuments } from '@/lib/services/documents.service';
import { makePort, makeClient } from '../helpers/factories';
describe('documents hub eoi_queue tab', () => {
describe('documents hub - eoi_queue tab', () => {
it('lists only EOIs in in-flight status', async () => {
const port = await makePort();
const client = await makeClient({ portId: port.id });

View File

@@ -1,5 +1,5 @@
/**
* Task 7 listDocuments folder filtering (TDD).
* Task 7 - listDocuments folder filtering (TDD).
*
* Exercises the three folderId modes: null (root only), a string (direct
* children), and a string with includeDescendants=true (subtree). Mirrors the

View File

@@ -1,5 +1,5 @@
/**
* PR8 expense duplicate detection.
* PR8 - expense duplicate detection.
*
* Validates:
* 1. `scanForDuplicates` matches by port + lower(vendor) + amount + date ±3d

View File

@@ -1,5 +1,5 @@
/**
* Task 9 entity-aggregated API query params (TDD).
* Task 9 - entity-aggregated API query params (TDD).
*
* Verifies:
* 1. listFilesAggregatedByEntity returns DIRECTLY ATTACHED + FROM COMPANY
@@ -71,7 +71,7 @@ async function insertFile(
// ─── listFilesAggregatedByEntity ──────────────────────────────────────────────
describe('GET /api/v1/files?entityType=client&entityId=… service layer', () => {
describe('GET /api/v1/files?entityType=client&entityId=… - service layer', () => {
let portId: string;
let clientId: string;
let companyId: string;
@@ -127,18 +127,18 @@ describe('GET /api/v1/files?entityType=client&entityId=… — service layer', (
const otherFile = await insertFile(otherPort.id, { clientId: otherClient.id });
const result = await listFilesAggregatedByEntity(portId, 'client', clientId);
// Groups are only for the correct port the other-port client's file must not appear
// Groups are only for the correct port - the other-port client's file must not appear
const allFileIds = result.groups.flatMap((g) => g.files.map((f) => (f as { id: string }).id));
expect(result.groups.length).toBeGreaterThan(0);
expect(allFileIds.length).toBeGreaterThan(0);
// Explicit cross-port isolation assertion leakage would cause this to fail
// Explicit cross-port isolation assertion - leakage would cause this to fail
expect(allFileIds).not.toContain(otherFile.id);
});
});
// ─── listInflightWorkflowsAggregatedByEntity ──────────────────────────────────
describe('GET /api/v1/documents?entityType=client&entityId=… service layer', () => {
describe('GET /api/v1/documents?entityType=client&entityId=… - service layer', () => {
let portId: string;
let clientId: string;

View File

@@ -18,7 +18,7 @@ import { makePort, makeClient, makeYacht } from '../helpers/factories';
let TEST_USER_ID = '';
beforeAll(async () => {
// Pull any existing user gdpr_exports.requested_by has an FK that needs
// Pull any existing user - gdpr_exports.requested_by has an FK that needs
// to resolve. Tests don't need the user to be specific; they just need it
// to exist.
const [u] = await db.select({ id: user.id }).from(user).limit(1);
@@ -166,7 +166,7 @@ describe('requestGdprExport', () => {
expect(add).toHaveBeenCalledWith(
'gdpr-export',
expect.objectContaining({ exportId: row.id, emailToClient: true }),
// F3: BullMQ 5.x rejects colons in custom job IDs switched to dash.
// F3: BullMQ 5.x rejects colons in custom job IDs - switched to dash.
expect.objectContaining({ jobId: `gdpr-export-${row.id}` }),
);

View File

@@ -8,7 +8,7 @@
*/
import { describe, it, expect, beforeAll } from 'vitest';
describe('interests.service port-scope FK validation', () => {
describe('interests.service - port-scope FK validation', () => {
let createInterest: typeof import('@/lib/services/interests.service').createInterest;
let updateInterest: typeof import('@/lib/services/interests.service').updateInterest;
let linkBerth: typeof import('@/lib/services/interests.service').linkBerth;

View File

@@ -15,7 +15,7 @@
*/
import { describe, it, expect, beforeAll } from 'vitest';
describe('interests.service yacht ownership validation', () => {
describe('interests.service - yacht ownership validation', () => {
let createInterest: typeof import('@/lib/services/interests.service').createInterest;
let updateInterest: typeof import('@/lib/services/interests.service').updateInterest;

View File

@@ -14,7 +14,7 @@
*/
import { describe, it, expect, beforeAll } from 'vitest';
describe('invoices.service billing entity', () => {
describe('invoices.service - billing entity', () => {
let createInvoice: typeof import('@/lib/services/invoices').createInvoice;
let makePort: typeof import('../helpers/factories').makePort;

View File

@@ -75,7 +75,7 @@ describe('gdpr-export-cleanup query semantics', () => {
expect(ids).toContain(expiredNoKey!.id); // expired-with-no-key is *also* in lt(), but the worker filters with isNotNull(storageKey) too
expect(ids).not.toContain(stillFresh!.id);
// The full worker filter (expires past, storageKey not null) only one row.
// The full worker filter (expires past, storageKey not null) - only one row.
const fullMatch = candidates.filter(
(r) => r.id !== expiredNoKey!.id && r.id !== stillFresh!.id,
);

View File

@@ -14,7 +14,7 @@
*/
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
// Socket and queue mocked these are tested in isolation here.
// Socket and queue mocked - these are tested in isolation here.
vi.mock('@/lib/socket/server', () => ({ emitToRoom: vi.fn() }));
vi.mock('@/lib/queue', () => ({
getQueue: () => ({ add: vi.fn().mockResolvedValue(undefined) }),
@@ -34,7 +34,7 @@ beforeAll(async () => {
dbAvailable = true;
} catch {
console.warn(
'[notification-lifecycle] Test database not available skipping integration tests',
'[notification-lifecycle] Test database not available - skipping integration tests',
);
}
});

View File

@@ -1,5 +1,5 @@
/**
* PR9 OCR config service.
* PR9 - OCR config service.
*
* Validates:
* 1. Per-port save/read round-trip (key encrypted at rest, decrypted on resolve)
@@ -108,7 +108,7 @@ describe('OCR config', () => {
{ provider: 'openai', model: 'gpt-4o-mini', apiKey: 'keep-me' },
'user-1',
);
// Update model only no apiKey field provided.
// Update model only - no apiKey field provided.
await saveOcrConfig(port.id, { provider: 'openai', model: 'gpt-4o' }, 'user-1');
const resolved = await getResolvedOcrConfig(port.id);
expect(resolved.apiKey).toBe('keep-me');

View File

@@ -66,7 +66,7 @@ async function checkPermission(
// ─── super_admin ──────────────────────────────────────────────────────────────
describe('Permission Matrix super_admin', () => {
describe('Permission Matrix - super_admin', () => {
const ctx = makeCtx({ isSuperAdmin: true, permissions: null });
it('can access clients.create', async () => {
@@ -88,7 +88,7 @@ describe('Permission Matrix — super_admin', () => {
// ─── viewer ───────────────────────────────────────────────────────────────────
describe('Permission Matrix viewer', () => {
describe('Permission Matrix - viewer', () => {
const ctx = makeCtx({ permissions: makeViewerPermissions() });
it('can view clients', async () => {
@@ -122,7 +122,7 @@ describe('Permission Matrix — viewer', () => {
// ─── sales_agent ─────────────────────────────────────────────────────────────
describe('Permission Matrix sales_agent', () => {
describe('Permission Matrix - sales_agent', () => {
const ctx = makeCtx({ permissions: makeSalesAgentPermissions() });
it('can view clients', async () => {
@@ -168,7 +168,7 @@ describe('Permission Matrix — sales_agent', () => {
// ─── sales_manager ────────────────────────────────────────────────────────────
describe('Permission Matrix sales_manager', () => {
describe('Permission Matrix - sales_manager', () => {
const ctx = makeCtx({ permissions: makeSalesManagerPermissions() });
it('can do everything with clients', async () => {
@@ -192,7 +192,7 @@ describe('Permission Matrix — sales_manager', () => {
// ─── director ─────────────────────────────────────────────────────────────────
describe('Permission Matrix director', () => {
describe('Permission Matrix - director', () => {
const ctx = makeCtx({ permissions: makeDirectorPermissions() });
it('can manage webhooks', async () => {
@@ -210,7 +210,7 @@ describe('Permission Matrix — director', () => {
// ─── deepMerge ────────────────────────────────────────────────────────────────
describe('deepMerge permission override merging', () => {
describe('deepMerge - permission override merging', () => {
it('overrides a single leaf value', () => {
const base = { clients: { view: true, create: false } };
const override = { clients: { create: true } };

View File

@@ -40,7 +40,7 @@ beforeAll(async () => {
await sql.end();
dbAvailable = true;
} catch {
console.warn('[pipeline-transitions] Test database not available skipping integration tests');
console.warn('[pipeline-transitions] Test database not available - skipping integration tests');
}
});

View File

@@ -1,7 +1,7 @@
/**
* Port-scoping integration tests (SECURITY-CRITICAL).
*
* Codex Addenda: Two-port testing every entity must be invisible
* Codex Addenda: Two-port testing - every entity must be invisible
* when queried under a different portId.
*
* Skips gracefully when TEST_DATABASE_URL is not reachable.
@@ -29,7 +29,7 @@ beforeAll(async () => {
await sql.end();
dbAvailable = true;
} catch {
console.warn('[port-scoping] Test database not available skipping integration tests');
console.warn('[port-scoping] Test database not available - skipping integration tests');
}
});
@@ -69,7 +69,7 @@ async function cleanupPorts(portA: string, portB: string): Promise<void> {
// ─── Tests ────────────────────────────────────────────────────────────────────
describe('Port Scoping Clients', () => {
describe('Port Scoping - Clients', () => {
let portA: string;
let portB: string;
@@ -153,7 +153,7 @@ describe('Port Scoping — Clients', () => {
});
});
describe('Port Scoping Interests', () => {
describe('Port Scoping - Interests', () => {
let portA: string;
let portB: string;
let clientIdA: string;

View File

@@ -1,5 +1,5 @@
/**
* Portal JWT verification locks in the audience/issuer hardening shipped
* Portal JWT verification - locks in the audience/issuer hardening shipped
* in fix(auth): a token without `aud: 'portal'` + `iss: 'pn-crm'` claims
* must NOT verify, even if it's signed with the correct shared secret.
*
@@ -50,7 +50,7 @@ describe('portal JWT', () => {
});
it('rejects a token missing the `aud: portal` claim', async () => {
// Issuer present, audience absent exactly the shape an old (pre-fix)
// Issuer present, audience absent - exactly the shape an old (pre-fix)
// portal session would have.
const token = await new SignJWT(SESSION as unknown as Record<string, unknown>)
.setProtectedHeader({ alg: 'HS256' })
@@ -74,7 +74,7 @@ describe('portal JWT', () => {
});
it('rejects a token with the wrong audience (CRM session replay shape)', async () => {
// What a better-auth session token might roughly look like same secret,
// What a better-auth session token might roughly look like - same secret,
// different audience. Must not verify against the portal path.
const token = await new SignJWT(SESSION as unknown as Record<string, unknown>)
.setProtectedHeader({ alg: 'HS256' })

View File

@@ -29,7 +29,7 @@ function uniqueIp(): string {
return `${IP_PREFIX}.${Math.floor(ipCounter / 255) % 255}.${ipCounter % 255}`;
}
describe('POST /api/public/interests trio creation', () => {
describe('POST /api/public/interests - trio creation', () => {
let POST: typeof import('@/app/api/public/interests/route').POST;
beforeAll(async () => {
@@ -212,7 +212,7 @@ describe('POST /api/public/interests — trio creation', () => {
.where(eq(interests.id, secondBody.data.id));
expect(secondInterest!.clientId).toBe(originalClientId);
// A second yacht row was created (not deduped) each submission is its
// A second yacht row was created (not deduped) - each submission is its
// own inquiry about a possibly-different yacht.
const clientsMatching = await db.select().from(clients).where(eq(clients.id, originalClientId));
expect(clientsMatching.length).toBe(1);
@@ -259,7 +259,7 @@ describe('POST /api/public/interests — trio creation', () => {
.where(eq(yachts.id, firstInterest!.yachtId!));
const originalCompanyId = firstYacht!.currentOwnerId;
// Second submission same company name, different casing, different client
// Second submission - same company name, different casing, different client
const secondReq = makeMockRequest(
'POST',
`http://localhost/api/public/interests?portId=${port.id}`,

View File

@@ -1,5 +1,5 @@
/**
* i18n PR910 public residential inquiry endpoint.
* i18n PR910 - public residential inquiry endpoint.
*
* Validates the server-side phone normalization that the public inquiry
* route runs when the website posts a raw international format (older
@@ -47,7 +47,7 @@ describe('POST /api/public/residential-inquiries', () => {
firstName: 'Anna',
lastName: 'Nowak',
email,
// Raw international format server should normalize.
// Raw international format - server should normalize.
phone: '+44 20 7946 0958',
placeOfResidence: 'Warsaw',
},
@@ -122,7 +122,7 @@ describe('POST /api/public/residential-inquiries', () => {
lastName: 'Lewandowska',
email,
phone: '22 555 0200', // National-format
phoneCountry: 'PL', // Hint only no E.164 yet.
phoneCountry: 'PL', // Hint only - no E.164 yet.
},
},
);

View File

@@ -13,7 +13,7 @@ import { describe, it, expect, beforeAll } from 'vitest';
import { db } from '@/lib/db';
import { interests } from '@/lib/db/schema/interests';
describe('recommendations.service yacht dimensions source', () => {
describe('recommendations.service - yacht dimensions source', () => {
let generateRecommendations: typeof import('@/lib/services/recommendations').generateRecommendations;
let makePort: typeof import('../helpers/factories').makePort;

View File

@@ -26,7 +26,7 @@ beforeAll(async () => {
dbAvailable = true;
} catch (err) {
console.warn(
'[schema-constraints] DATABASE_URL not reachable skipping integration tests',
'[schema-constraints] DATABASE_URL not reachable - skipping integration tests',
err,
);
}
@@ -61,7 +61,7 @@ describe('schema constraints', () => {
ownerType: 'client',
ownerId: clientB.id,
startDate: new Date(),
endDate: null, // another open row should violate partial unique
endDate: null, // another open row - should violate partial unique
createdBy: 'test',
}),
).rejects.toMatchObject({
@@ -123,7 +123,7 @@ describe('schema constraints', () => {
});
const berth = await makeBerth({ portId: port.id });
// Two ended reservations on same berth both should succeed
// Two ended reservations on same berth - both should succeed
// (partial index only constrains status='active').
await expect(
db.insert(berthReservations).values([

View File

@@ -14,7 +14,7 @@ import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import { makeAuditMeta } from '../helpers/factories';
// vi.mock is hoisted to the top of the module keep mocks there so vitest
// vi.mock is hoisted to the top of the module - keep mocks there so vitest
// doesn't warn about non-top-level calls. Use `vi.hoisted` for any mock that
// references a value (mockQueueAdd) so it's evaluated before the mock factory
// runs.
@@ -48,7 +48,7 @@ beforeAll(async () => {
await sql.end();
dbAvailable = true;
} catch {
console.warn('[webhook-delivery] Test database not available skipping integration tests');
console.warn('[webhook-delivery] Test database not available - skipping integration tests');
}
});