diff --git a/apps/api/src/trust-portal/trust-portal.service.spec.ts b/apps/api/src/trust-portal/trust-portal.service.spec.ts new file mode 100644 index 000000000..3ff54e7ce --- /dev/null +++ b/apps/api/src/trust-portal/trust-portal.service.spec.ts @@ -0,0 +1,88 @@ +import { db } from '@db'; +import { TrustPortalService } from './trust-portal.service'; + +jest.mock('@db', () => ({ + db: { + vendor: { + findMany: jest.fn(), + update: jest.fn(), + }, + globalVendors: { + findUnique: jest.fn(), + }, + }, + Prisma: {}, + TrustFramework: { + iso_27001: 'iso_27001', + iso_42001: 'iso_42001', + gdpr: 'gdpr', + hipaa: 'hipaa', + soc2_type1: 'soc2_type1', + soc2_type2: 'soc2_type2', + pci_dss: 'pci_dss', + nen_7510: 'nen_7510', + iso_9001: 'iso_9001', + }, +})); + +jest.mock('../app/s3', () => ({ + APP_AWS_ORG_ASSETS_BUCKET: 'org-assets', + s3Client: { send: jest.fn() }, + getSignedUrl: jest.fn(), +})); + +const mockDb = db as unknown as { + vendor: { findMany: jest.Mock; update: jest.Mock }; + globalVendors: { findUnique: jest.Mock }; +}; + +describe('TrustPortalService getAllVendorsWithSync compliance badges', () => { + const service = new TrustPortalService(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + // Regression: Scaleway's GlobalVendors record lists "ISO/IEC 27001:2022", + // "HDS" and "GDPR Compliance" as verified, but the Trust Centre only showed + // GDPR. The "IEC" infix and ":2022" suffix caused the ISO 27001 certification + // to be dropped during badge sync, understating the vendor's posture. + it('maps "ISO/IEC 27001:2022" to the iso27001 badge alongside gdpr', async () => { + const baseVendor = { + id: 'vnd_scaleway', + name: 'Scaleway', + description: null, + website: 'scaleway.com', + showOnTrustPortal: true, + logoUrl: 'https://logo.example/scaleway.png', + complianceBadges: null, + trustPortalOrder: 0, + }; + + mockDb.vendor.findMany.mockResolvedValue([baseVendor]); + mockDb.globalVendors.findUnique.mockResolvedValue({ + riskAssessmentData: { + certifications: [ + { type: 'ISO/IEC 27001:2022', status: 'verified' }, + { type: 'HDS', status: 'verified' }, + { type: 'GDPR Compliance', status: 'verified' }, + ], + }, + }); + mockDb.vendor.update.mockImplementation( + async ({ data }: { data: Record }) => ({ + ...baseVendor, + ...data, + }), + ); + + const result = await service.getAllVendorsWithSync('org_1'); + + const badgeTypes = ( + result[0].complianceBadges as unknown as Array<{ type: string }> + ).map((badge) => badge.type); + + expect(badgeTypes).toContain('iso27001'); + expect(badgeTypes).toContain('gdpr'); + }); +}); diff --git a/apps/api/src/trust-portal/trust-portal.service.ts b/apps/api/src/trust-portal/trust-portal.service.ts index f58099f8a..697ff9280 100644 --- a/apps/api/src/trust-portal/trust-portal.service.ts +++ b/apps/api/src/trust-portal/trust-portal.service.ts @@ -1950,10 +1950,11 @@ export class TrustPortalService { if (normalized.includes('soc2') || normalized.includes('soc 2')) return 'soc2'; - if (normalized.includes('iso27001') || normalized.includes('iso 27001')) - return 'iso27001'; - if (normalized.includes('iso42001') || normalized.includes('iso 42001')) - return 'iso42001'; + // Match ISO standards by their number. Vendors write these many ways + // ("ISO 27001", "ISO/IEC 27001:2022"), and the "IEC" infix breaks a naive + // includes('iso27001') check ("iso27001" is not a substring of "isoiec27001…"). + if (normalized.includes('27001')) return 'iso27001'; + if (normalized.includes('42001')) return 'iso42001'; if (normalized.includes('gdpr')) return 'gdpr'; if (normalized.includes('hipaa')) return 'hipaa'; if ( @@ -1964,8 +1965,7 @@ export class TrustPortalService { return 'pci_dss'; if (normalized.includes('nen7510') || normalized.includes('nen 7510')) return 'nen7510'; - if (normalized.includes('iso9001') || normalized.includes('iso 9001')) - return 'iso9001'; + if (normalized.includes('9001')) return 'iso9001'; return null; }