diff --git a/app/actions/supabase/coverage/toggle-exclusion.integration.test.ts b/app/actions/supabase/coverage/toggle-exclusion.integration.test.ts new file mode 100644 index 00000000..bba199f3 --- /dev/null +++ b/app/actions/supabase/coverage/toggle-exclusion.integration.test.ts @@ -0,0 +1,185 @@ +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-var-requires */ +import { toggleExclusion } from "./toggle-exclusion"; +import { supabaseAdmin } from "@/lib/supabase/server"; + +describe("toggleExclusion integration", () => { + const testOwnerId = Math.floor(Math.random() * 1000000) + 500000; + const testOwnerName = `test-owner-${Math.random().toString(36).substring(7)}`; + const testRepoId = Math.floor(Math.random() * 1000000) + 900000; + const testRepoName = `test-repo-${Date.now()}-${Math.random().toString(36).substring(7)}`; + const testUserId = Math.floor(Math.random() * 100000) + 10000; + const testUserName = `test-user-${Math.random().toString(36).substring(7)}`; + + beforeEach(async () => { + // Clean up any existing data + await supabaseAdmin + .from("coverages") + .delete() + .match({ owner_id: testOwnerId, repo_id: testRepoId }); + await supabaseAdmin + .from("repositories") + .delete() + .match({ owner_id: testOwnerId, repo_id: testRepoId }); + await supabaseAdmin.from("owners").delete().eq("owner_id", testOwnerId); + + // Create test owner + await supabaseAdmin.from("owners").insert({ + owner_id: testOwnerId, + owner_name: testOwnerName, + owner_type: "User", + stripe_customer_id: `test_customer_${testOwnerId}`, + }); + + // Create test repository + await supabaseAdmin.from("repositories").insert({ + owner_id: testOwnerId, + repo_id: testRepoId, + repo_name: testRepoName, + created_by: `${testUserId}:${testUserName}`, + updated_by: `${testUserId}:${testUserName}`, + }); + }); + + afterEach(async () => { + // Clean up test data + await supabaseAdmin + .from("coverages") + .delete() + .match({ owner_id: testOwnerId, repo_id: testRepoId }); + await supabaseAdmin + .from("repositories") + .delete() + .match({ owner_id: testOwnerId, repo_id: testRepoId }); + await supabaseAdmin.from("owners").delete().eq("owner_id", testOwnerId); + }); + + // ===== integration ===== + + describe("toggle exclusion scenarios", () => { + it("should toggle exclusion status for multiple files", async () => { + // Create test coverage records + const coverageRecords = [ + { + owner_id: testOwnerId, + repo_id: testRepoId, + branch_name: "main", + full_path: "src/file1.ts", + level: "file", + created_by: `${testUserId}:${testUserName}`, + updated_by: `${testUserId}:${testUserName}`, + is_excluded_from_testing: false, + }, + { + owner_id: testOwnerId, + repo_id: testRepoId, + branch_name: "main", + full_path: "src/file2.ts", + level: "file", + created_by: `${testUserId}:${testUserName}`, + updated_by: `${testUserId}:${testUserName}`, + is_excluded_from_testing: false, + }, + { + owner_id: testOwnerId, + repo_id: testRepoId, + branch_name: "main", + full_path: "src/file3.ts", + level: "file", + created_by: `${testUserId}:${testUserName}`, + updated_by: `${testUserId}:${testUserName}`, + is_excluded_from_testing: false, + }, + ]; + + const { data: insertedData, error: insertError } = await supabaseAdmin + .from("coverages") + .insert(coverageRecords) + .select(); + + expect(insertError).toBeNull(); + expect(insertedData).toBeDefined(); + + const ids = insertedData!.map((r) => r.id); + + // Toggle to excluded + const result = await toggleExclusion(ids, true, testUserId, testUserName); + expect(result).toBe(ids.length); + + // Verify in DB + const { data: updatedData } = await supabaseAdmin + .from("coverages") + .select("is_excluded_from_testing, updated_by") + .in("id", ids); + + expect(updatedData).toBeDefined(); + updatedData!.forEach((record) => { + expect(record.is_excluded_from_testing).toBe(true); + expect(record.updated_by).toBe(`${testUserId}:${testUserName}`); + }); + + // Toggle back to not excluded + const resultBack = await toggleExclusion(ids, false, testUserId, testUserName); + expect(resultBack).toBe(ids.length); + + const { data: updatedDataBack } = await supabaseAdmin + .from("coverages") + .select("is_excluded_from_testing") + .in("id", ids); + + updatedDataBack!.forEach((record) => { + expect(record.is_excluded_from_testing).toBe(false); + }); + }); + + it("should return 0 when no ids are provided", async () => { + const result = await toggleExclusion([], true, testUserId, testUserName); + expect(result).toBe(0); + }); + + it("should only update specified ids", async () => { + const coverageRecords = [ + { + owner_id: testOwnerId, + repo_id: testRepoId, + branch_name: "main", + full_path: "src/file1.ts", + level: "file", + created_by: `${testUserId}:${testUserName}`, + updated_by: `${testUserId}:${testUserName}`, + is_excluded_from_testing: false, + }, + { + owner_id: testOwnerId, + repo_id: testRepoId, + branch_name: "main", + full_path: "src/file2.ts", + level: "file", + created_by: `${testUserId}:${testUserName}`, + updated_by: `${testUserId}:${testUserName}`, + is_excluded_from_testing: false, + }, + ]; + + const { data: insertedData } = await supabaseAdmin + .from("coverages") + .insert(coverageRecords) + .select(); + + const ids = insertedData!.map((r) => r.id); + const idToToggle = [ids[0]]; + + await toggleExclusion(idToToggle, true, testUserId, testUserName); + + const { data: finalData } = await supabaseAdmin + .from("coverages") + .select("id, is_excluded_from_testing") + .in("id", ids); + + const record1 = finalData!.find((r) => r.id === ids[0]); + const record2 = finalData!.find((r) => r.id === ids[1]); + + expect(record1?.is_excluded_from_testing).toBe(true); + expect(record2?.is_excluded_from_testing).toBe(false); + }); + }); +}); diff --git a/app/actions/supabase/coverage/toggle-exclusion.test.ts b/app/actions/supabase/coverage/toggle-exclusion.test.ts new file mode 100644 index 00000000..38202742 --- /dev/null +++ b/app/actions/supabase/coverage/toggle-exclusion.test.ts @@ -0,0 +1,85 @@ +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-var-requires */ +import { toggleExclusion } from "./toggle-exclusion"; +import { supabaseAdmin } from "@/lib/supabase/server"; + +jest.mock("@/lib/supabase/server", () => ({ + supabaseAdmin: { + from: jest.fn(), + }, +})); + +describe("toggleExclusion", () => { + const mockFrom = supabaseAdmin.from as jest.Mock; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + // ===== solitary ===== + + describe("happy path", () => { + it("should return 0 when ids array is empty", async () => { + // Verify that if no IDs are provided, it returns 0 immediately without calling DB + const result = await toggleExclusion([], true, 1, "test-user"); + expect(result).toBe(0); + expect(mockFrom).not.toHaveBeenCalled(); + }); + + it("should update exclusion status and return number of updated ids", async () => { + const ids = [1, 2, 3]; + const isExcluded = true; + const userId = 123; + const userName = "test-user"; + + const mockIn = jest.fn().mockResolvedValue({ error: null }); + const mockUpdate = jest.fn().mockReturnValue({ in: mockIn }); + mockFrom.mockReturnValue({ update: mockUpdate }); + + const result = await toggleExclusion(ids, isExcluded, userId, userName); + + expect(mockFrom).toHaveBeenCalledWith("coverages"); + expect(mockUpdate).toHaveBeenCalledWith({ + is_excluded_from_testing: isExcluded, + updated_by: `${userId}:${userName}`, + }); + expect(mockIn).toHaveBeenCalledWith("id", ids); + expect(result).toBe(ids.length); + }); + + it("should work when toggling exclusion to false", async () => { + const ids = [1]; + const isExcluded = false; + const userId = 123; + const userName = "test-user"; + + const mockIn = jest.fn().mockResolvedValue({ error: null }); + const mockUpdate = jest.fn().mockReturnValue({ in: mockIn }); + mockFrom.mockReturnValue({ update: mockUpdate }); + + const result = await toggleExclusion(ids, isExcluded, userId, userName); + + expect(mockUpdate).toHaveBeenCalledWith({ + is_excluded_from_testing: false, + updated_by: `${userId}:${userName}`, + }); + expect(result).toBe(1); + }); + }); + + describe("error cases", () => { + it("should throw error and log to console when supabase update fails", async () => { + const ids = [1, 2]; + const mockError = { message: "Database error" }; + const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(); + + const mockIn = jest.fn().mockResolvedValue({ error: mockError }); + const mockUpdate = jest.fn().mockReturnValue({ in: mockIn }); + mockFrom.mockReturnValue({ update: mockUpdate }); + + await expect(toggleExclusion(ids, true, 1, "user")).rejects.toEqual(mockError); + expect(consoleErrorSpy).toHaveBeenCalledWith("Error toggling exclusion:", mockError); + + consoleErrorSpy.mockRestore(); + }); + }); +});