#!/usr/bin/env bun import { describe, test, expect, afterEach, beforeEach, spyOn } from "bun:test"; import { writeFile, unlink } from "fs/promises"; import { tmpdir } from "os"; import { join } from "path"; import { parseAndSetStructuredOutputs } from "../src/run-claude"; import * as core from "@actions/core"; // Mock execution file path const TEST_EXECUTION_FILE = join(tmpdir(), "test-execution-output.json"); // Helper to create mock execution file with structured output async function createMockExecutionFile( structuredOutput?: Record, includeResult: boolean = true, ): Promise { const messages: any[] = [ { type: "system", subtype: "init" }, { type: "turn", content: "test" }, ]; if (includeResult) { messages.push({ type: "result", cost_usd: 0.01, duration_ms: 1000, structured_output: structuredOutput, }); } await writeFile(TEST_EXECUTION_FILE, JSON.stringify(messages)); } // Spy on core functions let setOutputSpy: any; let infoSpy: any; beforeEach(() => { setOutputSpy = spyOn(core, "setOutput").mockImplementation(() => {}); infoSpy = spyOn(core, "info").mockImplementation(() => {}); }); describe("parseAndSetStructuredOutputs", () => { afterEach(async () => { setOutputSpy?.mockRestore(); infoSpy?.mockRestore(); try { await unlink(TEST_EXECUTION_FILE); } catch { // Ignore if file doesn't exist } }); test("should set structured_output with valid data", async () => { await createMockExecutionFile({ is_flaky: true, confidence: 0.85, summary: "Test looks flaky", }); await parseAndSetStructuredOutputs(TEST_EXECUTION_FILE); expect(setOutputSpy).toHaveBeenCalledWith( "structured_output", '{"is_flaky":true,"confidence":0.85,"summary":"Test looks flaky"}', ); expect(infoSpy).toHaveBeenCalledWith( "Set structured_output with 3 field(s)", ); }); test("should handle arrays and nested objects", async () => { await createMockExecutionFile({ items: ["a", "b", "c"], config: { key: "value", nested: { deep: true } }, }); await parseAndSetStructuredOutputs(TEST_EXECUTION_FILE); const callArgs = setOutputSpy.mock.calls[0]; expect(callArgs[0]).toBe("structured_output"); const parsed = JSON.parse(callArgs[1]); expect(parsed).toEqual({ items: ["a", "b", "c"], config: { key: "value", nested: { deep: true } }, }); }); test("should handle special characters in field names", async () => { await createMockExecutionFile({ "test-result": "passed", "item.count": 10, "user@email": "test", }); await parseAndSetStructuredOutputs(TEST_EXECUTION_FILE); const callArgs = setOutputSpy.mock.calls[0]; const parsed = JSON.parse(callArgs[1]); expect(parsed["test-result"]).toBe("passed"); expect(parsed["item.count"]).toBe(10); expect(parsed["user@email"]).toBe("test"); }); test("should throw error when result exists but structured_output is undefined", async () => { const messages = [ { type: "system", subtype: "init" }, { type: "result", cost_usd: 0.01, duration_ms: 1000 }, ]; await writeFile(TEST_EXECUTION_FILE, JSON.stringify(messages)); await expect( parseAndSetStructuredOutputs(TEST_EXECUTION_FILE), ).rejects.toThrow( "json_schema was provided but Claude did not return structured_output", ); }); test("should throw error when no result message exists", async () => { const messages = [ { type: "system", subtype: "init" }, { type: "turn", content: "test" }, ]; await writeFile(TEST_EXECUTION_FILE, JSON.stringify(messages)); await expect( parseAndSetStructuredOutputs(TEST_EXECUTION_FILE), ).rejects.toThrow( "json_schema was provided but Claude did not return structured_output", ); }); test("should throw error with malformed JSON", async () => { await writeFile(TEST_EXECUTION_FILE, "{ invalid json"); await expect( parseAndSetStructuredOutputs(TEST_EXECUTION_FILE), ).rejects.toThrow(); }); test("should throw error when file does not exist", async () => { await expect( parseAndSetStructuredOutputs("/nonexistent/file.json"), ).rejects.toThrow(); }); test("should handle empty structured_output object", async () => { await createMockExecutionFile({}); await parseAndSetStructuredOutputs(TEST_EXECUTION_FILE); expect(setOutputSpy).toHaveBeenCalledWith("structured_output", "{}"); expect(infoSpy).toHaveBeenCalledWith( "Set structured_output with 0 field(s)", ); }); });