fix(documents): tighten owner resolution + cover company/yacht paths

Three follow-ups from Task 7 code review:
1. Drop the dead interest.yachtId fallback branch. interests.clientId
   is NOT NULL so the yacht branch was unreachable. Comment explains
   the schema constraint so the branch can be re-added if that
   constraint is ever relaxed.
2. Add defense-in-depth port_id filter to the interests lookup
   inside resolveDocumentOwner (matches CLAUDE.md convention and
   every other interests query in this file).
3. Add two integration test cases for direct-company and direct-yacht
   owner resolution — closes the coverage gap where the signed-file
   row's companyId/yachtId columns are populated for the first time
   in this commit chain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-11 11:48:44 +02:00
parent ee6e3f3f3f
commit 8e2e2ea113
2 changed files with 103 additions and 20 deletions

View File

@@ -23,7 +23,7 @@ import { interests } from '@/lib/db/schema/interests';
import { user } from '@/lib/db/schema/users';
import { handleDocumentCompleted } from '@/lib/services/documents.service';
import { ensureSystemRoots } from '@/lib/services/document-folders.service';
import { makeClient, makePort } from '../helpers/factories';
import { makeClient, makeCompany, makePort, makeYacht } from '../helpers/factories';
// Stub Documenso download — do NOT hit the network.
vi.mock('@/lib/services/documenso-client', async (importOriginal) => {
@@ -116,10 +116,7 @@ describe('handleDocumentCompleted · auto-deposit', () => {
// Verify the folder is the entity folder for this client.
const folder = await db.query.documentFolders.findFirst({
where: and(
eq(documentFolders.entityType, 'client'),
eq(documentFolders.entityId, clientId),
),
where: and(eq(documentFolders.entityType, 'client'), eq(documentFolders.entityId, clientId)),
});
expect(folder).toBeDefined();
expect(fileRow?.folderId).toBe(folder!.id);
@@ -199,13 +196,91 @@ describe('handleDocumentCompleted · auto-deposit', () => {
expect(fileRow?.folderId).not.toBeNull();
// And the folder should be the client entity folder.
const folder = await db.query.documentFolders.findFirst({
where: and(eq(documentFolders.entityType, 'client'), eq(documentFolders.entityId, clientId)),
});
expect(folder).toBeDefined();
expect(fileRow?.folderId).toBe(folder!.id);
});
it('company-direct: signed PDF lands in the company subfolder', async () => {
const company = await makeCompany({ portId });
const documensoId = `docu-auto-deposit-company-${Date.now()}`;
const [doc] = await db
.insert(documents)
.values({
portId,
companyId: company.id,
documentType: 'other',
title: 'Auto-deposit test (company direct)',
status: 'partially_signed',
documensoId,
createdBy: 'seed',
})
.returning();
await handleDocumentCompleted({ documentId: documensoId, portId });
const updatedDoc = await db.query.documents.findFirst({
where: eq(documents.id, doc!.id),
});
expect(updatedDoc?.status).toBe('completed');
expect(updatedDoc?.signedFileId).not.toBeNull();
const fileRow = await db.query.files.findFirst({
where: eq(files.id, updatedDoc!.signedFileId!),
});
expect(fileRow?.companyId).toBe(company.id);
expect(fileRow?.folderId).not.toBeNull();
const folder = await db.query.documentFolders.findFirst({
where: and(
eq(documentFolders.entityType, 'client'),
eq(documentFolders.entityId, clientId),
eq(documentFolders.entityType, 'company'),
eq(documentFolders.entityId, company.id),
),
});
expect(folder).toBeDefined();
expect(fileRow?.folderId).toBe(folder!.id);
});
it('yacht-direct: signed PDF lands in the yacht subfolder', async () => {
// Yachts require a real owner client per schema constraint.
const ownerClient = await makeClient({ portId });
const yacht = await makeYacht({ portId, ownerType: 'client', ownerId: ownerClient.id });
const documensoId = `docu-auto-deposit-yacht-${Date.now()}`;
const [doc] = await db
.insert(documents)
.values({
portId,
yachtId: yacht.id,
documentType: 'other',
title: 'Auto-deposit test (yacht direct)',
status: 'partially_signed',
documensoId,
createdBy: 'seed',
})
.returning();
await handleDocumentCompleted({ documentId: documensoId, portId });
const updatedDoc = await db.query.documents.findFirst({
where: eq(documents.id, doc!.id),
});
expect(updatedDoc?.status).toBe('completed');
expect(updatedDoc?.signedFileId).not.toBeNull();
const fileRow = await db.query.files.findFirst({
where: eq(files.id, updatedDoc!.signedFileId!),
});
expect(fileRow?.yachtId).toBe(yacht.id);
expect(fileRow?.folderId).not.toBeNull();
const folder = await db.query.documentFolders.findFirst({
where: and(eq(documentFolders.entityType, 'yacht'), eq(documentFolders.entityId, yacht.id)),
});
expect(folder).toBeDefined();
expect(fileRow?.folderId).toBe(folder!.id);
});
});