fix(documents): tighten aggregation — filter ended memberships + symmetry
Four follow-ups from Task 8 code review: 1. Aggregation now filters companyMemberships to active rows only (isNull(endDate)) on both client→companies and company→clients joins. Previously a rep who left a company 2y ago would still see that company's files in their aggregated view. Brings this service in line with the 8 other call sites in the codebase that already filter on endDate. 2. Move collectRelatedEntities import to the top of documents.service.ts — was wedged mid-file. 3. listInflightWorkflowsAggregatedByEntity now calls assertEntityInPort for symmetry with the files version. Cross- port reads short-circuit early instead of executing N empty port-scoped queries. 4. Add a cross-port leakage regression test for the workflow projection. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,16 +16,11 @@ import { and, eq } from 'drizzle-orm';
|
||||
|
||||
import { db } from '@/lib/db';
|
||||
import { files, documents, documentFolders } from '@/lib/db/schema/documents';
|
||||
import { clients } from '@/lib/db/schema/clients';
|
||||
import { user } from '@/lib/db/schema/users';
|
||||
import { yachts } from '@/lib/db/schema/yachts';
|
||||
import {
|
||||
ensureSystemRoots,
|
||||
ensureEntityFolder,
|
||||
} from '@/lib/services/document-folders.service';
|
||||
import {
|
||||
listFilesAggregatedByEntity,
|
||||
applyEntityFkFromFolder,
|
||||
} from '@/lib/services/files';
|
||||
import { ensureSystemRoots, ensureEntityFolder } from '@/lib/services/document-folders.service';
|
||||
import { listFilesAggregatedByEntity, applyEntityFkFromFolder } from '@/lib/services/files';
|
||||
import { listInflightWorkflowsAggregatedByEntity } from '@/lib/services/documents.service';
|
||||
import { makePort, makeClient, makeCompany, makeYacht, makeMembership } from '../helpers/factories';
|
||||
|
||||
@@ -133,9 +128,7 @@ describe('files service · listFilesAggregatedByEntity', () => {
|
||||
clientId = client.id;
|
||||
|
||||
// Insert 25 files all with clientId
|
||||
await Promise.all(
|
||||
Array.from({ length: 25 }, () => insertFile(portId, { clientId })),
|
||||
);
|
||||
await Promise.all(Array.from({ length: 25 }, () => insertFile(portId, { clientId })));
|
||||
});
|
||||
|
||||
it('DIRECTLY ATTACHED group has 20 files and total=25', async () => {
|
||||
@@ -265,6 +258,16 @@ describe('documents service · listInflightWorkflowsAggregatedByEntity', () => {
|
||||
expect(directGroup!.workflows[0]!.status).toBe('sent');
|
||||
expect(directGroup!.workflows[0]!.title).toBe('In-flight Doc');
|
||||
});
|
||||
|
||||
it('rejects cross-port leakage with defense-in-depth port filter', async () => {
|
||||
const otherPort = await makePort();
|
||||
const [otherClient] = await db
|
||||
.insert(clients)
|
||||
.values({ portId: otherPort.id, fullName: 'Other Port Client' })
|
||||
.returning();
|
||||
const result = await listInflightWorkflowsAggregatedByEntity(portId, 'client', otherClient!.id);
|
||||
expect(result.groups).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── applyEntityFkFromFolder ──────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user