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
224 lines
7.6 KiB
TypeScript
224 lines
7.6 KiB
TypeScript
/**
|
|
* 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.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
|
|
import { and, eq } from 'drizzle-orm';
|
|
|
|
import { db } from '@/lib/db';
|
|
import { documentFolders, files, documents } from '@/lib/db/schema/documents';
|
|
import { user } from '@/lib/db/schema/users';
|
|
import { runBackfill } from '../../scripts/backfill-document-folders';
|
|
import { makePort, makeClient } from '../helpers/factories';
|
|
|
|
let TEST_USER_ID = '';
|
|
|
|
beforeAll(async () => {
|
|
const [u] = await db.select({ id: user.id }).from(user).limit(1);
|
|
if (!u) throw new Error('No user available; run pnpm db:seed first');
|
|
TEST_USER_ID = u.id;
|
|
});
|
|
|
|
describe('backfill-document-folders · runBackfill', () => {
|
|
let portId: string;
|
|
|
|
beforeEach(async () => {
|
|
const port = await makePort();
|
|
portId = port.id;
|
|
// Clean up any folders left by a prior test on this port.
|
|
await db.delete(documentFolders).where(eq(documentFolders.portId, portId));
|
|
});
|
|
|
|
// ── Test 1: Creates system roots and entity subfolders ─────────────────────
|
|
|
|
it('creates the three system roots and a client entity subfolder', async () => {
|
|
const client = await makeClient({ portId });
|
|
|
|
// Insert a file linked to the client so the backfill creates the subfolder.
|
|
await db.insert(files).values({
|
|
portId,
|
|
clientId: client.id,
|
|
filename: 'test.pdf',
|
|
originalName: 'test.pdf',
|
|
storagePath: `${portId}/test.pdf`,
|
|
storageBucket: 'crm-files',
|
|
uploadedBy: TEST_USER_ID,
|
|
});
|
|
|
|
await runBackfill({ portId, systemUserId: TEST_USER_ID });
|
|
|
|
const roots = await db
|
|
.select()
|
|
.from(documentFolders)
|
|
.where(and(eq(documentFolders.portId, portId), eq(documentFolders.entityType, 'root')));
|
|
|
|
expect(roots).toHaveLength(3);
|
|
const rootNames = roots.map((r) => r.name).sort();
|
|
expect(rootNames).toEqual(['Clients', 'Companies', 'Yachts']);
|
|
|
|
const entityFolder = await db.query.documentFolders.findFirst({
|
|
where: and(
|
|
eq(documentFolders.portId, portId),
|
|
eq(documentFolders.entityType, 'client'),
|
|
eq(documentFolders.entityId, client.id),
|
|
),
|
|
});
|
|
expect(entityFolder).toBeDefined();
|
|
expect(entityFolder?.entityType).toBe('client');
|
|
expect(entityFolder?.entityId).toBe(client.id);
|
|
});
|
|
|
|
// ── Test 2: Sets files.folder_id from entity FKs ────────────────────────────
|
|
|
|
it('sets files.folder_id for files that have entity FKs but no folder_id', async () => {
|
|
const client = await makeClient({ portId });
|
|
|
|
const [fileRow] = await db
|
|
.insert(files)
|
|
.values({
|
|
portId,
|
|
clientId: client.id,
|
|
filename: 'contract.pdf',
|
|
originalName: 'contract.pdf',
|
|
storagePath: `${portId}/contract.pdf`,
|
|
storageBucket: 'crm-files',
|
|
uploadedBy: TEST_USER_ID,
|
|
// folderId intentionally omitted → null
|
|
})
|
|
.returning();
|
|
|
|
expect(fileRow!.folderId).toBeNull();
|
|
|
|
await runBackfill({ portId, systemUserId: TEST_USER_ID });
|
|
|
|
const updated = await db.query.files.findFirst({
|
|
where: and(eq(files.id, fileRow!.id), eq(files.portId, portId)),
|
|
});
|
|
|
|
expect(updated!.folderId).not.toBeNull();
|
|
|
|
// The assigned folder must be the client's entity subfolder.
|
|
const entityFolder = await db.query.documentFolders.findFirst({
|
|
where: and(
|
|
eq(documentFolders.portId, portId),
|
|
eq(documentFolders.entityType, 'client'),
|
|
eq(documentFolders.entityId, client.id),
|
|
),
|
|
});
|
|
expect(updated!.folderId).toBe(entityFolder!.id);
|
|
});
|
|
|
|
// ── Test 3: Copies entity FKs from completed workflows onto signed files ────
|
|
|
|
it('propagates entity FKs from completed workflow onto signed file and sets folder_id', async () => {
|
|
const client = await makeClient({ portId });
|
|
|
|
// File row with no entity FK (simulating a legacy completed-before-auto-deposit).
|
|
const [signedFile] = await db
|
|
.insert(files)
|
|
.values({
|
|
portId,
|
|
// No clientId set - simulates legacy completion before entity FK auto-propagation.
|
|
filename: 'signed-eoi.pdf',
|
|
originalName: 'signed-eoi.pdf',
|
|
storagePath: `${portId}/signed-eoi.pdf`,
|
|
storageBucket: 'crm-files',
|
|
uploadedBy: TEST_USER_ID,
|
|
})
|
|
.returning();
|
|
|
|
expect(signedFile!.clientId).toBeNull();
|
|
|
|
// Completed workflow document pointing at the orphaned file.
|
|
await db.insert(documents).values({
|
|
portId,
|
|
documentType: 'eoi',
|
|
title: 'EOI for client',
|
|
status: 'completed',
|
|
signedFileId: signedFile!.id,
|
|
clientId: client.id,
|
|
createdBy: TEST_USER_ID,
|
|
});
|
|
|
|
await runBackfill({ portId, systemUserId: TEST_USER_ID });
|
|
|
|
const updatedFile = await db.query.files.findFirst({
|
|
where: and(eq(files.id, signedFile!.id), eq(files.portId, portId)),
|
|
});
|
|
|
|
// The backfill should have set the clientId on the signed file.
|
|
expect(updatedFile!.clientId).toBe(client.id);
|
|
// And then assigned it to the client's entity subfolder.
|
|
expect(updatedFile!.folderId).not.toBeNull();
|
|
});
|
|
|
|
// ── Test 4: Idempotent ────────────────────────────────────────────────────────
|
|
|
|
it('is idempotent - running twice produces the same number of folder rows', async () => {
|
|
const client = await makeClient({ portId });
|
|
|
|
await db.insert(files).values({
|
|
portId,
|
|
clientId: client.id,
|
|
filename: 'idempotent.pdf',
|
|
originalName: 'idempotent.pdf',
|
|
storagePath: `${portId}/idempotent.pdf`,
|
|
storageBucket: 'crm-files',
|
|
uploadedBy: TEST_USER_ID,
|
|
});
|
|
|
|
await runBackfill({ portId, systemUserId: TEST_USER_ID });
|
|
|
|
const countAfterFirst = await db
|
|
.select()
|
|
.from(documentFolders)
|
|
.where(eq(documentFolders.portId, portId));
|
|
|
|
await runBackfill({ portId, systemUserId: TEST_USER_ID });
|
|
|
|
const countAfterSecond = await db
|
|
.select()
|
|
.from(documentFolders)
|
|
.where(eq(documentFolders.portId, portId));
|
|
|
|
expect(countAfterSecond).toHaveLength(countAfterFirst.length);
|
|
});
|
|
|
|
// ── Test 5: Port isolation ────────────────────────────────────────────────────
|
|
|
|
it('does not create folders for a different port when only portId is supplied', async () => {
|
|
const otherPort = await makePort();
|
|
await db.delete(documentFolders).where(eq(documentFolders.portId, otherPort.id));
|
|
|
|
const otherClient = await makeClient({ portId: otherPort.id });
|
|
|
|
await db.insert(files).values({
|
|
portId: otherPort.id,
|
|
clientId: otherClient.id,
|
|
filename: 'other-port.pdf',
|
|
originalName: 'other-port.pdf',
|
|
storagePath: `${otherPort.id}/other-port.pdf`,
|
|
storageBucket: 'crm-files',
|
|
uploadedBy: TEST_USER_ID,
|
|
});
|
|
|
|
// Run backfill only for the main portId, NOT the otherPort.
|
|
await runBackfill({ portId, systemUserId: TEST_USER_ID });
|
|
|
|
const otherPortFolders = await db
|
|
.select()
|
|
.from(documentFolders)
|
|
.where(eq(documentFolders.portId, otherPort.id));
|
|
|
|
// The other port should have zero folders - the backfill was not run for it.
|
|
expect(otherPortFolders).toHaveLength(0);
|
|
});
|
|
});
|