Skip to content
This repository was archived by the owner on Jul 14, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 1 addition & 23 deletions src/tools/yamlMutations/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,14 @@ 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 = {
testTargetId: string;
filePath: string;
};

const getRelevantTestCases = (
testCasesById: Record<string, SyncTestCase>,
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");
Expand Down
73 changes: 73 additions & 0 deletions src/tools/yamlMutations/getRelevantTestCases.ts
Original file line number Diff line number Diff line change
@@ -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<string, SyncTestCase>,
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 = (
Comment thread
fabianboth marked this conversation as resolved.
testCasesById: Record<string, SyncTestCase>,
startTestCase: SyncTestCase,
): SyncTestCase[] => {
const visited = new Set<string>();
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;
};
58 changes: 0 additions & 58 deletions tests/tools/yamlMutations/edit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" });

Expand Down
178 changes: 178 additions & 0 deletions tests/tools/yamlMutations/getRelevantTestCases.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});