/** * interests.service tenant-FK validation tests. * * Covers the security fix that pins clientId/berthId/yachtId on interest * create/update + linkBerth to the caller's port. Without these guards a * port-A caller could splice a port-B FK onto their own interest and * surface foreign-tenant data through the join surfaces in getInterestById. */ import { describe, it, expect, beforeAll } from 'vitest'; 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; let makePort: typeof import('../helpers/factories').makePort; let makeClient: typeof import('../helpers/factories').makeClient; let makeBerth: typeof import('../helpers/factories').makeBerth; let makeYacht: typeof import('../helpers/factories').makeYacht; let makeAuditMeta: typeof import('../helpers/factories').makeAuditMeta; beforeAll(async () => { const svc = await import('@/lib/services/interests.service'); createInterest = svc.createInterest; updateInterest = svc.updateInterest; linkBerth = svc.linkBerth; const factories = await import('../helpers/factories'); makePort = factories.makePort; makeClient = factories.makeClient; makeBerth = factories.makeBerth; makeYacht = factories.makeYacht; makeAuditMeta = factories.makeAuditMeta; }); it('rejects createInterest with a foreign-port clientId', async () => { const portA = await makePort(); const portB = await makePort(); const foreignClient = await makeClient({ portId: portB.id }); await expect( createInterest( portA.id, { clientId: foreignClient.id, pipelineStage: 'open', tagIds: [], reminderEnabled: false, }, makeAuditMeta({ portId: portA.id }), ), ).rejects.toThrow(/clientId not found in this port/); }); it('rejects createInterest with a foreign-port berthId', async () => { const portA = await makePort(); const portB = await makePort(); const localClient = await makeClient({ portId: portA.id }); const foreignBerth = await makeBerth({ portId: portB.id }); await expect( createInterest( portA.id, { clientId: localClient.id, berthId: foreignBerth.id, pipelineStage: 'open', tagIds: [], reminderEnabled: false, }, makeAuditMeta({ portId: portA.id }), ), ).rejects.toThrow(/berthId not found in this port/); }); it('rejects createInterest with a foreign-port yachtId', async () => { const portA = await makePort(); const portB = await makePort(); const localClient = await makeClient({ portId: portA.id }); const foreignClient = await makeClient({ portId: portB.id }); const foreignYacht = await makeYacht({ portId: portB.id, ownerType: 'client', ownerId: foreignClient.id, }); await expect( createInterest( portA.id, { clientId: localClient.id, yachtId: foreignYacht.id, pipelineStage: 'open', tagIds: [], reminderEnabled: false, }, makeAuditMeta({ portId: portA.id }), ), ).rejects.toThrow(/yachtId not found in this port/); }); it('rejects updateInterest swapping in a foreign-port berthId', async () => { const portA = await makePort(); const portB = await makePort(); const localClient = await makeClient({ portId: portA.id }); const foreignBerth = await makeBerth({ portId: portB.id }); const interest = await createInterest( portA.id, { clientId: localClient.id, pipelineStage: 'open', tagIds: [], reminderEnabled: false, }, makeAuditMeta({ portId: portA.id }), ); await expect( updateInterest( interest.id, portA.id, { berthId: foreignBerth.id }, makeAuditMeta({ portId: portA.id }), ), ).rejects.toThrow(/berthId not found in this port/); }); it('rejects linkBerth with a foreign-port berthId', async () => { const portA = await makePort(); const portB = await makePort(); const localClient = await makeClient({ portId: portA.id }); const foreignBerth = await makeBerth({ portId: portB.id }); const interest = await createInterest( portA.id, { clientId: localClient.id, pipelineStage: 'open', tagIds: [], reminderEnabled: false, }, makeAuditMeta({ portId: portA.id }), ); await expect( linkBerth(interest.id, portA.id, foreignBerth.id, makeAuditMeta({ portId: portA.id })), ).rejects.toThrow(/berthId not found in this port/); }); });