From 6347978c95aad2bb84549f474e026fc41ae0b071 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 21:25:09 +0000 Subject: [PATCH 1/4] Initial plan From 07e81ec5f0f63e852588f12bca938d18b599651d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 21:30:39 +0000 Subject: [PATCH 2/4] feat: improve run details step summary with bullet points and version field Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../setup/js/generate_workflow_overview.cjs | 15 +++++-------- .../js/generate_workflow_overview.test.cjs | 22 ++++++++++++------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/actions/setup/js/generate_workflow_overview.cjs b/actions/setup/js/generate_workflow_overview.cjs index f80e7b41ddd..9a40946e623 100644 --- a/actions/setup/js/generate_workflow_overview.cjs +++ b/actions/setup/js/generate_workflow_overview.cjs @@ -32,17 +32,14 @@ async function generateWorkflowOverview(core) { "
\n" + "Run details\n\n" + "#### Engine Configuration\n" + - "| Property | Value |\n" + - "|----------|-------|\n" + - `| Engine ID | ${awInfo.engine_id} |\n` + - `| Engine Name | ${awInfo.engine_name} |\n` + - `| Model | ${awInfo.model || "(default)"} |\n` + + `- **Version**: ${awInfo.version || "(unknown)"}\n` + + `- **Engine ID**: ${awInfo.engine_id}\n` + + `- **Engine Name**: ${awInfo.engine_name}\n` + + `- **Model**: ${awInfo.model || "(default)"}\n` + "\n" + "#### Network Configuration\n" + - "| Property | Value |\n" + - "|----------|-------|\n" + - `| Firewall | ${awInfo.firewall_enabled ? "✅ Enabled" : "❌ Disabled"} |\n` + - `| Firewall Version | ${awInfo.awf_version || "(latest)"} |\n` + + `- **Firewall**: ${awInfo.firewall_enabled ? "✅ Enabled" : "❌ Disabled"}\n` + + `- **Firewall Version**: ${awInfo.awf_version || "(latest)"}\n` + "\n" + (networkDetails ? `##### Allowed Domains\n${networkDetails}\n` : "") + "
"; diff --git a/actions/setup/js/generate_workflow_overview.test.cjs b/actions/setup/js/generate_workflow_overview.test.cjs index 2e60f77fecc..67d99aa1f0d 100644 --- a/actions/setup/js/generate_workflow_overview.test.cjs +++ b/actions/setup/js/generate_workflow_overview.test.cjs @@ -54,6 +54,7 @@ describe("generate_workflow_overview.cjs", () => { engine_id: "copilot", engine_name: "GitHub Copilot", model: "gpt-4", + version: "v1.2.3", firewall_enabled: true, awf_version: "1.0.0", allowed_domains: [], @@ -69,13 +70,17 @@ describe("generate_workflow_overview.cjs", () => { expect(summaryArg).toContain("
"); expect(summaryArg).toContain("Run details"); expect(summaryArg).toContain("#### Engine Configuration"); - expect(summaryArg).toContain("| Engine ID | copilot |"); - expect(summaryArg).toContain("| Engine Name | GitHub Copilot |"); - expect(summaryArg).toContain("| Model | gpt-4 |"); + expect(summaryArg).toContain("- **Version**: v1.2.3"); + expect(summaryArg).toContain("- **Engine ID**: copilot"); + expect(summaryArg).toContain("- **Engine Name**: GitHub Copilot"); + expect(summaryArg).toContain("- **Model**: gpt-4"); expect(summaryArg).toContain("#### Network Configuration"); - expect(summaryArg).toContain("| Firewall | ✅ Enabled |"); - expect(summaryArg).toContain("| Firewall Version | 1.0.0 |"); + expect(summaryArg).toContain("- **Firewall**: ✅ Enabled"); + expect(summaryArg).toContain("- **Firewall Version**: 1.0.0"); expect(summaryArg).toContain("
"); + // Ensure no table syntax is present + expect(summaryArg).not.toContain("| Property | Value |"); + expect(summaryArg).not.toContain("|----------|-------|"); }); it("should handle missing optional fields with defaults", async () => { @@ -90,9 +95,10 @@ describe("generate_workflow_overview.cjs", () => { await generateWorkflowOverview(mockCore); const summaryArg = mockCore.summary.addRaw.mock.calls[0][0]; - expect(summaryArg).toContain("| Model | (default) |"); - expect(summaryArg).toContain("| Firewall | ❌ Disabled |"); - expect(summaryArg).toContain("| Firewall Version | (latest) |"); + expect(summaryArg).toContain("- **Version**: (unknown)"); + expect(summaryArg).toContain("- **Model**: (default)"); + expect(summaryArg).toContain("- **Firewall**: ❌ Disabled"); + expect(summaryArg).toContain("- **Firewall Version**: (latest)"); }); it("should include allowed domains when present (up to 10)", async () => { From 3c922a68572c4c8965f2af74cb582fb7f35261aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 21:43:14 +0000 Subject: [PATCH 3/4] feat: show engine_id+version in summary tag, render all aw_info fields via json_object_to_markdown helper Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../setup/js/generate_workflow_overview.cjs | 34 +++----- .../js/generate_workflow_overview.test.cjs | 79 ++++++------------- actions/setup/js/json_object_to_markdown.cjs | 70 ++++++++++++++++ .../setup/js/json_object_to_markdown.test.cjs | 61 ++++++++++++++ 4 files changed, 166 insertions(+), 78 deletions(-) create mode 100644 actions/setup/js/json_object_to_markdown.cjs create mode 100644 actions/setup/js/json_object_to_markdown.test.cjs diff --git a/actions/setup/js/generate_workflow_overview.cjs b/actions/setup/js/generate_workflow_overview.cjs index 9a40946e623..126599a3322 100644 --- a/actions/setup/js/generate_workflow_overview.cjs +++ b/actions/setup/js/generate_workflow_overview.cjs @@ -1,6 +1,8 @@ // @ts-check /// +const { jsonObjectToMarkdown } = require("./json_object_to_markdown.cjs"); + /** * Generate workflow overview step that writes an agentic workflow run overview * to the GitHub step summary. This reads from aw_info.json that was created by @@ -16,33 +18,15 @@ async function generateWorkflowOverview(core) { // Load aw_info.json const awInfo = JSON.parse(fs.readFileSync(awInfoPath, "utf8")); - let networkDetails = ""; - if (awInfo.allowed_domains && awInfo.allowed_domains.length > 0) { - networkDetails = awInfo.allowed_domains - .slice(0, 10) - .map(d => ` - ${d}`) - .join("\n"); - if (awInfo.allowed_domains.length > 10) { - networkDetails += `\n - ... and ${awInfo.allowed_domains.length - 10} more`; - } - } + // Build the collapsible summary label with engine_id and version + const engineLabel = [awInfo.engine_id, awInfo.version].filter(Boolean).join(" "); + const summaryLabel = engineLabel ? `Run details - ${engineLabel}` : "Run details"; + + // Render all aw_info fields as markdown bullet points + const details = jsonObjectToMarkdown(awInfo); // Build summary using string concatenation to avoid YAML parsing issues with template literals - const summary = - "
\n" + - "Run details\n\n" + - "#### Engine Configuration\n" + - `- **Version**: ${awInfo.version || "(unknown)"}\n` + - `- **Engine ID**: ${awInfo.engine_id}\n` + - `- **Engine Name**: ${awInfo.engine_name}\n` + - `- **Model**: ${awInfo.model || "(default)"}\n` + - "\n" + - "#### Network Configuration\n" + - `- **Firewall**: ${awInfo.firewall_enabled ? "✅ Enabled" : "❌ Disabled"}\n` + - `- **Firewall Version**: ${awInfo.awf_version || "(latest)"}\n` + - "\n" + - (networkDetails ? `##### Allowed Domains\n${networkDetails}\n` : "") + - "
"; + const summary = "
\n" + `${summaryLabel}\n\n` + details + "\n" + "
"; await core.summary.addRaw(summary).write(); console.log("Generated workflow overview in step summary"); diff --git a/actions/setup/js/generate_workflow_overview.test.cjs b/actions/setup/js/generate_workflow_overview.test.cjs index 67d99aa1f0d..5a2d7e0f566 100644 --- a/actions/setup/js/generate_workflow_overview.test.cjs +++ b/actions/setup/js/generate_workflow_overview.test.cjs @@ -1,7 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import fs from "fs"; -import os from "os"; -import path from "path"; // Mock the global objects that GitHub Actions provides const mockCore = { @@ -22,9 +20,7 @@ global.core = mockCore; describe("generate_workflow_overview.cjs", () => { let generateWorkflowOverview; - let tmpDir; let awInfoPath; - let originalRequireCache; beforeEach(async () => { // Reset mocks @@ -68,23 +64,22 @@ describe("generate_workflow_overview.cjs", () => { const summaryArg = mockCore.summary.addRaw.mock.calls[0][0]; expect(summaryArg).toContain("
"); - expect(summaryArg).toContain("Run details"); - expect(summaryArg).toContain("#### Engine Configuration"); - expect(summaryArg).toContain("- **Version**: v1.2.3"); - expect(summaryArg).toContain("- **Engine ID**: copilot"); - expect(summaryArg).toContain("- **Engine Name**: GitHub Copilot"); - expect(summaryArg).toContain("- **Model**: gpt-4"); - expect(summaryArg).toContain("#### Network Configuration"); - expect(summaryArg).toContain("- **Firewall**: ✅ Enabled"); - expect(summaryArg).toContain("- **Firewall Version**: 1.0.0"); + // engine_id and version should appear in the summary label + expect(summaryArg).toContain("Run details - copilot v1.2.3"); + // All fields should be rendered as bullet points + expect(summaryArg).toContain("- **engine_id**: copilot"); + expect(summaryArg).toContain("- **engine_name**: GitHub Copilot"); + expect(summaryArg).toContain("- **model**: gpt-4"); + expect(summaryArg).toContain("- **version**: v1.2.3"); + expect(summaryArg).toContain("- **firewall_enabled**: true"); + expect(summaryArg).toContain("- **awf_version**: 1.0.0"); expect(summaryArg).toContain("
"); // Ensure no table syntax is present expect(summaryArg).not.toContain("| Property | Value |"); expect(summaryArg).not.toContain("|----------|-------|"); }); - it("should handle missing optional fields with defaults", async () => { - // Create test aw_info.json with minimal fields + it("should show only engine_id in summary label when version is missing", async () => { const awInfo = { engine_id: "claude", engine_name: "Claude", @@ -95,63 +90,41 @@ describe("generate_workflow_overview.cjs", () => { await generateWorkflowOverview(mockCore); const summaryArg = mockCore.summary.addRaw.mock.calls[0][0]; - expect(summaryArg).toContain("- **Version**: (unknown)"); - expect(summaryArg).toContain("- **Model**: (default)"); - expect(summaryArg).toContain("- **Firewall**: ❌ Disabled"); - expect(summaryArg).toContain("- **Firewall Version**: (latest)"); - }); - - it("should include allowed domains when present (up to 10)", async () => { - const awInfo = { - engine_id: "copilot", - engine_name: "GitHub Copilot", - firewall_enabled: true, - allowed_domains: ["example.com", "github.com", "api.github.com"], - }; - fs.writeFileSync(awInfoPath, JSON.stringify(awInfo)); - - await generateWorkflowOverview(mockCore); - - const summaryArg = mockCore.summary.addRaw.mock.calls[0][0]; - expect(summaryArg).toContain("##### Allowed Domains"); - expect(summaryArg).toContain(" - example.com"); - expect(summaryArg).toContain(" - github.com"); - expect(summaryArg).toContain(" - api.github.com"); + expect(summaryArg).toContain("Run details - claude"); + expect(summaryArg).toContain("- **engine_id**: claude"); + expect(summaryArg).toContain("- **firewall_enabled**: false"); }); - it("should truncate allowed domains list when more than 10", async () => { - const domains = Array.from({ length: 15 }, (_, i) => `domain${i + 1}.com`); + it("should show plain 'Run details' in summary label when both engine_id and version are missing", async () => { const awInfo = { - engine_id: "copilot", - engine_name: "GitHub Copilot", - firewall_enabled: true, - allowed_domains: domains, + engine_name: "Unknown Engine", }; fs.writeFileSync(awInfoPath, JSON.stringify(awInfo)); await generateWorkflowOverview(mockCore); const summaryArg = mockCore.summary.addRaw.mock.calls[0][0]; - expect(summaryArg).toContain("##### Allowed Domains"); - expect(summaryArg).toContain(" - domain1.com"); - expect(summaryArg).toContain(" - domain10.com"); - expect(summaryArg).toContain(" - ... and 5 more"); - expect(summaryArg).not.toContain("domain11.com"); + expect(summaryArg).toContain("Run details"); }); - it("should not include Allowed Domains section when empty", async () => { + it("should render all fields from aw_info including nested objects and arrays", async () => { const awInfo = { engine_id: "copilot", - engine_name: "GitHub Copilot", - firewall_enabled: false, - allowed_domains: [], + version: "v2.0.0", + allowed_domains: ["example.com", "github.com"], + steps: { firewall: "iptables" }, }; fs.writeFileSync(awInfoPath, JSON.stringify(awInfo)); await generateWorkflowOverview(mockCore); const summaryArg = mockCore.summary.addRaw.mock.calls[0][0]; - expect(summaryArg).not.toContain("##### Allowed Domains"); + expect(summaryArg).toContain("- **engine_id**: copilot"); + expect(summaryArg).toContain("- **allowed_domains**:"); + expect(summaryArg).toContain(" - example.com"); + expect(summaryArg).toContain(" - github.com"); + expect(summaryArg).toContain("- **steps**:"); + expect(summaryArg).toContain(" - **firewall**: iptables"); }); it("should log success message", async () => { diff --git a/actions/setup/js/json_object_to_markdown.cjs b/actions/setup/js/json_object_to_markdown.cjs new file mode 100644 index 00000000000..7633e682bc8 --- /dev/null +++ b/actions/setup/js/json_object_to_markdown.cjs @@ -0,0 +1,70 @@ +// @ts-check + +/** + * JSON Object to Markdown Converter + * + * Converts a plain JavaScript object to a Markdown bullet list. + * Handles nested objects (with indentation), arrays, and primitive values. + */ + +/** + * Format a single value as a readable string for Markdown output. + * @param {unknown} value - The value to format + * @returns {string} - String representation of the value + */ +function formatValue(value) { + if (value === null || value === undefined || value === "") { + return "(none)"; + } + if (Array.isArray(value)) { + return value.length === 0 ? "(none)" : ""; + } + if (typeof value === "object") { + return ""; + } + return String(value); +} + +/** + * Convert a plain JavaScript object to Markdown bullet points. + * Nested objects and arrays are rendered as indented sub-lists. + * + * @param {Record} obj - The object to render + * @param {number} [depth=0] - Current indentation depth + * @returns {string} - Markdown bullet list string + */ +function jsonObjectToMarkdown(obj, depth = 0) { + if (!obj || typeof obj !== "object" || Array.isArray(obj)) { + return ""; + } + + const indent = " ".repeat(depth); + const lines = []; + + for (const [key, value] of Object.entries(obj)) { + if (Array.isArray(value)) { + if (value.length === 0) { + lines.push(`${indent}- **${key}**: (none)`); + } else { + lines.push(`${indent}- **${key}**:`); + for (const item of value) { + if (typeof item === "object" && item !== null) { + lines.push(jsonObjectToMarkdown(/** @type {Record} */ item, depth + 1)); + } else { + lines.push(`${" ".repeat(depth + 1)}- ${String(item)}`); + } + } + } + } else if (typeof value === "object" && value !== null) { + lines.push(`${indent}- **${key}**:`); + lines.push(jsonObjectToMarkdown(/** @type {Record} */ value, depth + 1)); + } else { + const formatted = formatValue(value); + lines.push(`${indent}- **${key}**: ${formatted}`); + } + } + + return lines.join("\n"); +} + +module.exports = { jsonObjectToMarkdown }; diff --git a/actions/setup/js/json_object_to_markdown.test.cjs b/actions/setup/js/json_object_to_markdown.test.cjs new file mode 100644 index 00000000000..1294115e581 --- /dev/null +++ b/actions/setup/js/json_object_to_markdown.test.cjs @@ -0,0 +1,61 @@ +import { describe, it, expect } from "vitest"; + +const { jsonObjectToMarkdown } = await import("./json_object_to_markdown.cjs"); + +describe("json_object_to_markdown.cjs", () => { + it("should render flat key-value pairs as bold-key bullet points", () => { + const obj = { engine_id: "copilot", version: "v1.0.0" }; + const result = jsonObjectToMarkdown(obj); + expect(result).toContain("- **engine_id**: copilot"); + expect(result).toContain("- **version**: v1.0.0"); + }); + + it("should render boolean values as true/false strings", () => { + const obj = { firewall_enabled: true, staged: false }; + const result = jsonObjectToMarkdown(obj); + expect(result).toContain("- **firewall_enabled**: true"); + expect(result).toContain("- **staged**: false"); + }); + + it("should render null/undefined/empty string values as (none)", () => { + const obj = { model: "", awf_version: null, agent_version: undefined }; + const result = jsonObjectToMarkdown(obj); + expect(result).toContain("- **model**: (none)"); + expect(result).toContain("- **awf_version**: (none)"); + expect(result).toContain("- **agent_version**: (none)"); + }); + + it("should render non-empty arrays as sub-bullet lists", () => { + const obj = { allowed_domains: ["example.com", "github.com"] }; + const result = jsonObjectToMarkdown(obj); + expect(result).toContain("- **allowed_domains**:"); + expect(result).toContain(" - example.com"); + expect(result).toContain(" - github.com"); + }); + + it("should render empty arrays as (none)", () => { + const obj = { allowed_domains: [] }; + const result = jsonObjectToMarkdown(obj); + expect(result).toContain("- **allowed_domains**: (none)"); + }); + + it("should render nested objects as indented sub-bullet lists", () => { + const obj = { steps: { firewall: "iptables" } }; + const result = jsonObjectToMarkdown(obj); + expect(result).toContain("- **steps**:"); + expect(result).toContain(" - **firewall**: iptables"); + }); + + it("should return empty string for null or non-object input", () => { + expect(jsonObjectToMarkdown(null)).toBe(""); + expect(jsonObjectToMarkdown(undefined)).toBe(""); + expect(jsonObjectToMarkdown([])).toBe(""); + }); + + it("should handle numeric values", () => { + const obj = { run_id: 12345, run_number: 7 }; + const result = jsonObjectToMarkdown(obj); + expect(result).toContain("- **run_id**: 12345"); + expect(result).toContain("- **run_number**: 7"); + }); +}); From f57cc36eded3173426fa1a0a33722f96f8b303f8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:08:29 +0000 Subject: [PATCH 4/4] feat: humanify keys in json_object_to_markdown (replace _ and - with spaces) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../js/generate_workflow_overview.test.cjs | 18 ++++----- actions/setup/js/json_object_to_markdown.cjs | 21 +++++++--- .../setup/js/json_object_to_markdown.test.cjs | 38 ++++++++++++++----- 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/actions/setup/js/generate_workflow_overview.test.cjs b/actions/setup/js/generate_workflow_overview.test.cjs index 5a2d7e0f566..c87e666af38 100644 --- a/actions/setup/js/generate_workflow_overview.test.cjs +++ b/actions/setup/js/generate_workflow_overview.test.cjs @@ -66,13 +66,13 @@ describe("generate_workflow_overview.cjs", () => { expect(summaryArg).toContain("
"); // engine_id and version should appear in the summary label expect(summaryArg).toContain("Run details - copilot v1.2.3"); - // All fields should be rendered as bullet points - expect(summaryArg).toContain("- **engine_id**: copilot"); - expect(summaryArg).toContain("- **engine_name**: GitHub Copilot"); + // All fields should be rendered as bullet points with humanified keys + expect(summaryArg).toContain("- **engine id**: copilot"); + expect(summaryArg).toContain("- **engine name**: GitHub Copilot"); expect(summaryArg).toContain("- **model**: gpt-4"); expect(summaryArg).toContain("- **version**: v1.2.3"); - expect(summaryArg).toContain("- **firewall_enabled**: true"); - expect(summaryArg).toContain("- **awf_version**: 1.0.0"); + expect(summaryArg).toContain("- **firewall enabled**: true"); + expect(summaryArg).toContain("- **awf version**: 1.0.0"); expect(summaryArg).toContain("
"); // Ensure no table syntax is present expect(summaryArg).not.toContain("| Property | Value |"); @@ -91,8 +91,8 @@ describe("generate_workflow_overview.cjs", () => { const summaryArg = mockCore.summary.addRaw.mock.calls[0][0]; expect(summaryArg).toContain("Run details - claude"); - expect(summaryArg).toContain("- **engine_id**: claude"); - expect(summaryArg).toContain("- **firewall_enabled**: false"); + expect(summaryArg).toContain("- **engine id**: claude"); + expect(summaryArg).toContain("- **firewall enabled**: false"); }); it("should show plain 'Run details' in summary label when both engine_id and version are missing", async () => { @@ -119,8 +119,8 @@ describe("generate_workflow_overview.cjs", () => { await generateWorkflowOverview(mockCore); const summaryArg = mockCore.summary.addRaw.mock.calls[0][0]; - expect(summaryArg).toContain("- **engine_id**: copilot"); - expect(summaryArg).toContain("- **allowed_domains**:"); + expect(summaryArg).toContain("- **engine id**: copilot"); + expect(summaryArg).toContain("- **allowed domains**:"); expect(summaryArg).toContain(" - example.com"); expect(summaryArg).toContain(" - github.com"); expect(summaryArg).toContain("- **steps**:"); diff --git a/actions/setup/js/json_object_to_markdown.cjs b/actions/setup/js/json_object_to_markdown.cjs index 7633e682bc8..bd8f2e4c984 100644 --- a/actions/setup/js/json_object_to_markdown.cjs +++ b/actions/setup/js/json_object_to_markdown.cjs @@ -7,6 +7,16 @@ * Handles nested objects (with indentation), arrays, and primitive values. */ +/** + * Humanify a JSON key by replacing underscores and hyphens with spaces. + * e.g. "engine_id" → "engine id", "awf-version" → "awf version" + * @param {string} key - The raw object key + * @returns {string} - Human-readable key + */ +function humanifyKey(key) { + return key.replace(/[_-]/g, " "); +} + /** * Format a single value as a readable string for Markdown output. * @param {unknown} value - The value to format @@ -42,11 +52,12 @@ function jsonObjectToMarkdown(obj, depth = 0) { const lines = []; for (const [key, value] of Object.entries(obj)) { + const label = humanifyKey(key); if (Array.isArray(value)) { if (value.length === 0) { - lines.push(`${indent}- **${key}**: (none)`); + lines.push(`${indent}- **${label}**: (none)`); } else { - lines.push(`${indent}- **${key}**:`); + lines.push(`${indent}- **${label}**:`); for (const item of value) { if (typeof item === "object" && item !== null) { lines.push(jsonObjectToMarkdown(/** @type {Record} */ item, depth + 1)); @@ -56,15 +67,15 @@ function jsonObjectToMarkdown(obj, depth = 0) { } } } else if (typeof value === "object" && value !== null) { - lines.push(`${indent}- **${key}**:`); + lines.push(`${indent}- **${label}**:`); lines.push(jsonObjectToMarkdown(/** @type {Record} */ value, depth + 1)); } else { const formatted = formatValue(value); - lines.push(`${indent}- **${key}**: ${formatted}`); + lines.push(`${indent}- **${label}**: ${formatted}`); } } return lines.join("\n"); } -module.exports = { jsonObjectToMarkdown }; +module.exports = { humanifyKey, jsonObjectToMarkdown }; diff --git a/actions/setup/js/json_object_to_markdown.test.cjs b/actions/setup/js/json_object_to_markdown.test.cjs index 1294115e581..5662295e708 100644 --- a/actions/setup/js/json_object_to_markdown.test.cjs +++ b/actions/setup/js/json_object_to_markdown.test.cjs @@ -1,19 +1,37 @@ import { describe, it, expect } from "vitest"; -const { jsonObjectToMarkdown } = await import("./json_object_to_markdown.cjs"); +const { humanifyKey, jsonObjectToMarkdown } = await import("./json_object_to_markdown.cjs"); describe("json_object_to_markdown.cjs", () => { - it("should render flat key-value pairs as bold-key bullet points", () => { + describe("humanifyKey", () => { + it("should replace underscores with spaces", () => { + expect(humanifyKey("engine_id")).toBe("engine id"); + expect(humanifyKey("firewall_enabled")).toBe("firewall enabled"); + expect(humanifyKey("awf_version")).toBe("awf version"); + }); + + it("should replace hyphens with spaces", () => { + expect(humanifyKey("run-id")).toBe("run id"); + expect(humanifyKey("awf-version")).toBe("awf version"); + }); + + it("should leave keys without separators unchanged", () => { + expect(humanifyKey("version")).toBe("version"); + expect(humanifyKey("model")).toBe("model"); + }); + }); + + it("should render flat key-value pairs with humanified keys", () => { const obj = { engine_id: "copilot", version: "v1.0.0" }; const result = jsonObjectToMarkdown(obj); - expect(result).toContain("- **engine_id**: copilot"); + expect(result).toContain("- **engine id**: copilot"); expect(result).toContain("- **version**: v1.0.0"); }); it("should render boolean values as true/false strings", () => { const obj = { firewall_enabled: true, staged: false }; const result = jsonObjectToMarkdown(obj); - expect(result).toContain("- **firewall_enabled**: true"); + expect(result).toContain("- **firewall enabled**: true"); expect(result).toContain("- **staged**: false"); }); @@ -21,14 +39,14 @@ describe("json_object_to_markdown.cjs", () => { const obj = { model: "", awf_version: null, agent_version: undefined }; const result = jsonObjectToMarkdown(obj); expect(result).toContain("- **model**: (none)"); - expect(result).toContain("- **awf_version**: (none)"); - expect(result).toContain("- **agent_version**: (none)"); + expect(result).toContain("- **awf version**: (none)"); + expect(result).toContain("- **agent version**: (none)"); }); it("should render non-empty arrays as sub-bullet lists", () => { const obj = { allowed_domains: ["example.com", "github.com"] }; const result = jsonObjectToMarkdown(obj); - expect(result).toContain("- **allowed_domains**:"); + expect(result).toContain("- **allowed domains**:"); expect(result).toContain(" - example.com"); expect(result).toContain(" - github.com"); }); @@ -36,7 +54,7 @@ describe("json_object_to_markdown.cjs", () => { it("should render empty arrays as (none)", () => { const obj = { allowed_domains: [] }; const result = jsonObjectToMarkdown(obj); - expect(result).toContain("- **allowed_domains**: (none)"); + expect(result).toContain("- **allowed domains**: (none)"); }); it("should render nested objects as indented sub-bullet lists", () => { @@ -55,7 +73,7 @@ describe("json_object_to_markdown.cjs", () => { it("should handle numeric values", () => { const obj = { run_id: 12345, run_number: 7 }; const result = jsonObjectToMarkdown(obj); - expect(result).toContain("- **run_id**: 12345"); - expect(result).toContain("- **run_number**: 7"); + expect(result).toContain("- **run id**: 12345"); + expect(result).toContain("- **run number**: 7"); }); });