From 2ad769a6cbb82352637847ec90ca06ff48e27ecb Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Oct 2025 05:20:10 +0000 Subject: [PATCH] Critical: Add comprehensive test coverage for backward compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add test cases for the primary use case addressed by this PR: validating OrganizationReportSummary objects without the newApplications field. Tests added: - isOrganizationReportSummary accepts objects without newApplications - isOrganizationReportSummary accepts objects with undefined newApplications - validateOrganizationReportSummary accepts objects without newApplications - validateOrganizationReportSummary accepts objects with undefined newApplications - Validation enforces empty string rejection when newApplications is present - Validation enforces array length limits when newApplications is present - Validation enforces string length limits when newApplications is present This prevents regression where legacy encrypted data (which predates the newApplications field) would fail to decrypt. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../domain/risk-insights-type-guards.spec.ts | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-type-guards.spec.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-type-guards.spec.ts index 6c130dc77fa..7475c669032 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-type-guards.spec.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/domain/risk-insights-type-guards.spec.ts @@ -247,6 +247,76 @@ describe("Risk Insights Type Guards", () => { /Invalid OrganizationReportSummary/, ); }); + + // Backward compatibility tests - legacy encrypted data predates newApplications field + it("should accept OrganizationReportSummary without newApplications field (backward compatibility)", () => { + const validLegacyData = { + totalMemberCount: 10, + totalApplicationCount: 5, + totalAtRiskMemberCount: 2, + totalAtRiskApplicationCount: 1, + totalCriticalApplicationCount: 3, + totalCriticalMemberCount: 4, + totalCriticalAtRiskMemberCount: 1, + totalCriticalAtRiskApplicationCount: 1, + // newApplications field intentionally omitted - legacy data + }; + + expect(() => validateOrganizationReportSummary(validLegacyData)).not.toThrow(); + expect(validateOrganizationReportSummary(validLegacyData)).toEqual(validLegacyData); + }); + + it("should accept OrganizationReportSummary with undefined newApplications (backward compatibility)", () => { + const validData = { + totalMemberCount: 10, + totalApplicationCount: 5, + totalAtRiskMemberCount: 2, + totalAtRiskApplicationCount: 1, + totalCriticalApplicationCount: 3, + totalCriticalMemberCount: 4, + totalCriticalAtRiskMemberCount: 1, + totalCriticalAtRiskApplicationCount: 1, + newApplications: undefined, + }; + + expect(() => validateOrganizationReportSummary(validData)).not.toThrow(); + }); + + it("should enforce array length limits on newApplications when present", () => { + const invalidData = { + totalMemberCount: 10, + totalApplicationCount: 5, + totalAtRiskMemberCount: 2, + totalAtRiskApplicationCount: 1, + totalCriticalApplicationCount: 3, + totalCriticalMemberCount: 4, + totalCriticalAtRiskMemberCount: 1, + totalCriticalAtRiskApplicationCount: 1, + newApplications: new Array(50001).fill("app"), // exceeds MAX_ARRAY_LENGTH + }; + + expect(() => validateOrganizationReportSummary(invalidData)).toThrow( + /Invalid OrganizationReportSummary.*newApplications/, + ); + }); + + it("should enforce string length limits on newApplications when present", () => { + const invalidData = { + totalMemberCount: 10, + totalApplicationCount: 5, + totalAtRiskMemberCount: 2, + totalAtRiskApplicationCount: 1, + totalCriticalApplicationCount: 3, + totalCriticalMemberCount: 4, + totalCriticalAtRiskMemberCount: 1, + totalCriticalAtRiskApplicationCount: 1, + newApplications: ["app-1", "a".repeat(1001)], // exceeds MAX_STRING_LENGTH + }; + + expect(() => validateOrganizationReportSummary(invalidData)).toThrow( + /Invalid OrganizationReportSummary.*newApplications/, + ); + }); }); describe("validateOrganizationReportApplicationArray", () => { @@ -616,6 +686,82 @@ describe("Risk Insights Type Guards", () => { }; expect(isOrganizationReportSummary(invalidData)).toBe(false); }); + + // Backward compatibility tests - legacy encrypted data predates newApplications field + it("should return true for OrganizationReportSummary without newApplications field (backward compatibility)", () => { + const validLegacyData = { + totalMemberCount: 10, + totalApplicationCount: 5, + totalAtRiskMemberCount: 2, + totalAtRiskApplicationCount: 1, + totalCriticalApplicationCount: 3, + totalCriticalMemberCount: 4, + totalCriticalAtRiskMemberCount: 1, + totalCriticalAtRiskApplicationCount: 1, + // newApplications field intentionally omitted - legacy data + }; + expect(isOrganizationReportSummary(validLegacyData)).toBe(true); + }); + + it("should return true for OrganizationReportSummary with undefined newApplications (backward compatibility)", () => { + const validData = { + totalMemberCount: 10, + totalApplicationCount: 5, + totalAtRiskMemberCount: 2, + totalAtRiskApplicationCount: 1, + totalCriticalApplicationCount: 3, + totalCriticalMemberCount: 4, + totalCriticalAtRiskMemberCount: 1, + totalCriticalAtRiskApplicationCount: 1, + newApplications: undefined, + }; + expect(isOrganizationReportSummary(validData)).toBe(true); + }); + + it("should return false for empty string in newApplications when present", () => { + const invalidData = { + totalMemberCount: 10, + totalApplicationCount: 5, + totalAtRiskMemberCount: 2, + totalAtRiskApplicationCount: 1, + totalCriticalApplicationCount: 3, + totalCriticalMemberCount: 4, + totalCriticalAtRiskMemberCount: 1, + totalCriticalAtRiskApplicationCount: 1, + newApplications: ["app-1", "", "app-3"], // empty string should be rejected + }; + expect(isOrganizationReportSummary(invalidData)).toBe(false); + }); + + it("should return false when newApplications exceeds array length limit", () => { + const invalidData = { + totalMemberCount: 10, + totalApplicationCount: 5, + totalAtRiskMemberCount: 2, + totalAtRiskApplicationCount: 1, + totalCriticalApplicationCount: 3, + totalCriticalMemberCount: 4, + totalCriticalAtRiskMemberCount: 1, + totalCriticalAtRiskApplicationCount: 1, + newApplications: new Array(50001).fill("app"), // exceeds MAX_ARRAY_LENGTH + }; + expect(isOrganizationReportSummary(invalidData)).toBe(false); + }); + + it("should return false when newApplications contains strings exceeding length limit", () => { + const invalidData = { + totalMemberCount: 10, + totalApplicationCount: 5, + totalAtRiskMemberCount: 2, + totalAtRiskApplicationCount: 1, + totalCriticalApplicationCount: 3, + totalCriticalMemberCount: 4, + totalCriticalAtRiskMemberCount: 1, + totalCriticalAtRiskApplicationCount: 1, + newApplications: ["app-1", "a".repeat(1001)], // exceeds MAX_STRING_LENGTH + }; + expect(isOrganizationReportSummary(invalidData)).toBe(false); + }); }); describe("isOrganizationReportApplication", () => {