diff --git a/src/tools/yamlMutations/edit.ts b/src/tools/yamlMutations/edit.ts index 05f4e52..78723b9 100644 --- a/src/tools/yamlMutations/edit.ts +++ b/src/tools/yamlMutations/edit.ts @@ -16,6 +16,7 @@ import { checkForConsistency } from "../sync/consistency"; import { draftPush } from "../sync/push"; import { SyncTestCase } from "../sync/types"; import { readTestCasesFromDir, writeSingleTestCaseYaml } from "../sync/yaml"; +import { getRelevantTestCases } from "./getRelevantTestCases"; import { waitForLocalChangesToBeFinished } from "./waitForLocalChanges"; type EditOptions = { @@ -23,29 +24,6 @@ type EditOptions = { filePath: string; }; -const getRelevantTestCases = ( - testCasesById: Record, - startTestCase: SyncTestCase, -): SyncTestCase[] => { - let dependencyId = startTestCase.dependencyId; - const result: SyncTestCase[] = [startTestCase]; - - while (dependencyId) { - const currentTestCase = testCasesById[dependencyId]; - - if (!currentTestCase) { - throw new Error( - `Could not find dependency ${dependencyId} for ${startTestCase.id}`, - ); - } - - result.push(currentTestCase); - dependencyId = currentTestCase?.dependencyId; - } - - return result; -}; - const loadTestCase = (testCasePath: string): SyncTestCase => { try { const content = fs.readFileSync(testCasePath, "utf8"); diff --git a/src/tools/yamlMutations/getRelevantTestCases.ts b/src/tools/yamlMutations/getRelevantTestCases.ts new file mode 100644 index 0000000..d274c49 --- /dev/null +++ b/src/tools/yamlMutations/getRelevantTestCases.ts @@ -0,0 +1,73 @@ +import { SyncTestCase } from "../sync/types"; + +type LinkedTestCaseId = { + id: string; + linkType: "dependency" | "teardown"; + sourceTestCaseId: string; +}; + +const getLinkedTestCaseIds = (testCase: SyncTestCase): LinkedTestCaseId[] => { + const links: LinkedTestCaseId[] = []; + + if (testCase.dependencyId) { + links.push({ + id: testCase.dependencyId, + linkType: "dependency", + sourceTestCaseId: testCase.id, + }); + } + + if (testCase.teardownId) { + links.push({ + id: testCase.teardownId, + linkType: "teardown", + sourceTestCaseId: testCase.id, + }); + } + + return links; +}; + +const resolveLinkedTestCase = ( + testCasesById: Record, + link: LinkedTestCaseId, +): SyncTestCase => { + const testCase = testCasesById[link.id]; + + if (!testCase) { + throw new Error( + `Could not find ${link.linkType} ${link.id} for ${link.sourceTestCaseId}`, + ); + } + + return testCase; +}; + +export const getRelevantTestCases = ( + testCasesById: Record, + startTestCase: SyncTestCase, +): SyncTestCase[] => { + const visited = new Set(); + const result: SyncTestCase[] = []; + const queue: SyncTestCase[] = [startTestCase]; + + let currentTestCase = queue.shift(); + while (currentTestCase) { + if (!visited.has(currentTestCase.id)) { + visited.add(currentTestCase.id); + result.push(currentTestCase); + + const links = getLinkedTestCaseIds(currentTestCase); + for (const link of links) { + if (!visited.has(link.id)) { + const linkedTestCase = resolveLinkedTestCase(testCasesById, link); + queue.push(linkedTestCase); + } + } + } + + currentTestCase = queue.shift(); + } + + return result; +}; diff --git a/tests/tools/yamlMutations/edit.spec.ts b/tests/tools/yamlMutations/edit.spec.ts index 5ba2301..4602013 100644 --- a/tests/tools/yamlMutations/edit.spec.ts +++ b/tests/tools/yamlMutations/edit.spec.ts @@ -140,64 +140,6 @@ describe("edit", () => { ); }); - it("includes dependency chain in relevant test cases", async () => { - const parentTestCase = createMockSyncTestCase({ id: "parent-id" }); - const childTestCase = createMockSyncTestCase({ - id: "child-id", - dependencyId: "parent-id", - }); - - vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind"); - vi.mocked(getAbsoluteFilePathInOctomindRoot).mockResolvedValue( - "/mock/.octomind/child.yaml", - ); - vi.mocked(fs.readFileSync).mockReturnValue(yaml.stringify(childTestCase)); - vi.mocked(readTestCasesFromDir).mockReturnValue([ - parentTestCase, - childTestCase, - ]); - vi.mocked(draftPush).mockResolvedValue({ - success: true, - versionIds: [], - syncDataByStableId: { "child-id": { versionId: "version-123" } }, - }); - vi.mocked(mockedClient.GET).mockResolvedValue({ - data: { localEditingStatus: "CANCELLED" }, - error: undefined, - response: mock(), - }); - - await edit({ testTargetId: "someId", filePath: "child.yaml" }); - - expect(draftPush).toHaveBeenCalledWith( - expect.objectContaining({ - testCases: expect.arrayContaining([ - expect.objectContaining({ id: "child-id" }), - expect.objectContaining({ id: "parent-id" }), - ]), - }), - expect.anything(), - ); - }); - - it("throws if dependency is not found", async () => { - const childTestCase = createMockSyncTestCase({ - id: "child-id", - dependencyId: "missing-parent", - }); - - vi.mocked(findOctomindFolder).mockResolvedValue("/mock/.octomind"); - vi.mocked(getAbsoluteFilePathInOctomindRoot).mockResolvedValue( - "/mock/.octomind/child.yaml", - ); - vi.mocked(fs.readFileSync).mockReturnValue(yaml.stringify(childTestCase)); - vi.mocked(readTestCasesFromDir).mockReturnValue([childTestCase]); - - await expect( - edit({ testTargetId: "someId", filePath: "child.yaml" }), - ).rejects.toThrow("Could not find dependency missing-parent"); - }); - it("exits gracefully when editing is finished", async () => { const testCase = createMockSyncTestCase({ id: "test-id" }); diff --git a/tests/tools/yamlMutations/getRelevantTestCases.spec.ts b/tests/tools/yamlMutations/getRelevantTestCases.spec.ts new file mode 100644 index 0000000..95b01ff --- /dev/null +++ b/tests/tools/yamlMutations/getRelevantTestCases.spec.ts @@ -0,0 +1,178 @@ +import { describe, expect, it } from "vitest"; + +import { getRelevantTestCases } from "../../../src/tools/yamlMutations/getRelevantTestCases"; +import { createMockSyncTestCase } from "../../mocks"; + +describe("getRelevantTestCases", () => { + describe("dependency chain", () => { + it("returns only the start test case when it has no links", () => { + const testCase = createMockSyncTestCase({ id: "test-id" }); + const testCasesById = { "test-id": testCase }; + + const result = getRelevantTestCases(testCasesById, testCase); + + expect(result).toEqual([testCase]); + }); + + it("includes the dependency chain", () => { + const parent = createMockSyncTestCase({ id: "parent-id" }); + const child = createMockSyncTestCase({ + id: "child-id", + dependencyId: "parent-id", + }); + const testCasesById = { "parent-id": parent, "child-id": child }; + + const result = getRelevantTestCases(testCasesById, child); + + expect(result).toHaveLength(2); + expect(result).toContainEqual(child); + expect(result).toContainEqual(parent); + }); + + it("includes deeply nested dependency chain", () => { + const grandparent = createMockSyncTestCase({ id: "grandparent-id" }); + const parent = createMockSyncTestCase({ + id: "parent-id", + dependencyId: "grandparent-id", + }); + const child = createMockSyncTestCase({ + id: "child-id", + dependencyId: "parent-id", + }); + const testCasesById = { + "grandparent-id": grandparent, + "parent-id": parent, + "child-id": child, + }; + + const result = getRelevantTestCases(testCasesById, child); + + expect(result).toHaveLength(3); + expect(result).toContainEqual(child); + expect(result).toContainEqual(parent); + expect(result).toContainEqual(grandparent); + }); + + it("throws if dependency is not found", () => { + const child = createMockSyncTestCase({ + id: "child-id", + dependencyId: "missing-parent", + }); + const testCasesById = { "child-id": child }; + + expect(() => getRelevantTestCases(testCasesById, child)).toThrow( + "Could not find dependency missing-parent for child-id", + ); + }); + }); + + describe("teardown chain", () => { + it("includes the teardown test case", () => { + const testCase = createMockSyncTestCase({ + id: "test-id", + teardownId: "teardown-id", + }); + const teardown = createMockSyncTestCase({ id: "teardown-id" }); + const testCasesById = { "test-id": testCase, "teardown-id": teardown }; + + const result = getRelevantTestCases(testCasesById, testCase); + + expect(result).toHaveLength(2); + expect(result).toContainEqual(testCase); + expect(result).toContainEqual(teardown); + }); + + it("includes teardown's dependencies", () => { + const testCase = createMockSyncTestCase({ + id: "test-id", + teardownId: "teardown-id", + }); + const teardownDependency = createMockSyncTestCase({ + id: "teardown-dependency-id", + }); + const teardown = createMockSyncTestCase({ + id: "teardown-id", + dependencyId: "teardown-dependency-id", + }); + const testCasesById = { + "test-id": testCase, + "teardown-id": teardown, + "teardown-dependency-id": teardownDependency, + }; + + const result = getRelevantTestCases(testCasesById, testCase); + + expect(result).toHaveLength(3); + expect(result).toContainEqual(testCase); + expect(result).toContainEqual(teardown); + expect(result).toContainEqual(teardownDependency); + }); + + it("throws if teardown is not found", () => { + const testCase = createMockSyncTestCase({ + id: "test-id", + teardownId: "missing-teardown", + }); + const testCasesById = { "test-id": testCase }; + + expect(() => getRelevantTestCases(testCasesById, testCase)).toThrow( + "Could not find teardown missing-teardown for test-id", + ); + }); + }); + + describe("combined dependency and teardown", () => { + it("includes both dependency and teardown chains", () => { + const parent = createMockSyncTestCase({ id: "parent-id" }); + const teardown = createMockSyncTestCase({ id: "teardown-id" }); + const testCase = createMockSyncTestCase({ + id: "test-id", + dependencyId: "parent-id", + teardownId: "teardown-id", + }); + const testCasesById = { + "parent-id": parent, + "test-id": testCase, + "teardown-id": teardown, + }; + + const result = getRelevantTestCases(testCasesById, testCase); + + expect(result).toHaveLength(3); + expect(result).toContainEqual(testCase); + expect(result).toContainEqual(parent); + expect(result).toContainEqual(teardown); + }); + + it("handles shared test cases in dependency and teardown chains", () => { + const shared = createMockSyncTestCase({ id: "shared-id" }); + const parent = createMockSyncTestCase({ + id: "parent-id", + dependencyId: "shared-id", + }); + const teardown = createMockSyncTestCase({ + id: "teardown-id", + dependencyId: "shared-id", + }); + const testCase = createMockSyncTestCase({ + id: "test-id", + dependencyId: "parent-id", + teardownId: "teardown-id", + }); + const testCasesById = { + "shared-id": shared, + "parent-id": parent, + "test-id": testCase, + "teardown-id": teardown, + }; + + const result = getRelevantTestCases(testCasesById, testCase); + + expect(result).toHaveLength(4); + expect(result).toContainEqual(testCase); + expect(result).toContainEqual(parent); + expect(result).toContainEqual(teardown); + expect(result).toContainEqual(shared); + }); + }); +});