From 157dd71efa7836477c546ae5dc6628755325cbdc Mon Sep 17 00:00:00 2001 From: Kacper Date: Thu, 18 Dec 2025 14:58:48 +0100 Subject: [PATCH] test: enhance app specification and automaker paths tests - Added comprehensive tests for the `specToXml` function, covering various scenarios including minimal specs, XML escaping, and optional sections. - Updated tests for `getStructuredSpecPromptInstruction` and `getAppSpecFormatInstruction` to ensure they return valid instructions. - Refactored automaker paths tests to use `path.join` for cross-platform compatibility, ensuring correct directory paths are generated. --- .../tests/unit/lib/app-spec-format.test.ts | 198 +++++++++++++++--- .../tests/unit/lib/automaker-paths.test.ts | 33 +-- 2 files changed, 185 insertions(+), 46 deletions(-) diff --git a/apps/server/tests/unit/lib/app-spec-format.test.ts b/apps/server/tests/unit/lib/app-spec-format.test.ts index a2fdd11e..43eb5359 100644 --- a/apps/server/tests/unit/lib/app-spec-format.test.ts +++ b/apps/server/tests/unit/lib/app-spec-format.test.ts @@ -1,57 +1,189 @@ import { describe, it, expect } from "vitest"; import { - APP_SPEC_XML_FORMAT, + specToXml, + getStructuredSpecPromptInstruction, getAppSpecFormatInstruction, + APP_SPEC_XML_FORMAT, + type SpecOutput, } from "@/lib/app-spec-format.js"; describe("app-spec-format.ts", () => { - describe("APP_SPEC_XML_FORMAT", () => { - it("should export a non-empty string constant", () => { - expect(typeof APP_SPEC_XML_FORMAT).toBe("string"); - expect(APP_SPEC_XML_FORMAT.length).toBeGreaterThan(0); + describe("specToXml", () => { + it("should convert minimal spec to XML", () => { + const spec: SpecOutput = { + project_name: "Test Project", + overview: "A test project", + technology_stack: ["TypeScript", "Node.js"], + core_capabilities: ["Testing", "Development"], + implemented_features: [ + { name: "Feature 1", description: "First feature" }, + ], + }; + + const xml = specToXml(spec); + + expect(xml).toContain(''); + expect(xml).toContain(""); + expect(xml).toContain(""); + expect(xml).toContain("Test Project"); + expect(xml).toContain("TypeScript"); + expect(xml).toContain("Testing"); }); - it("should contain XML format documentation", () => { - expect(APP_SPEC_XML_FORMAT).toContain(""); - expect(APP_SPEC_XML_FORMAT).toContain(""); - expect(APP_SPEC_XML_FORMAT).toContain(""); - expect(APP_SPEC_XML_FORMAT).toContain(""); - expect(APP_SPEC_XML_FORMAT).toContain(""); - expect(APP_SPEC_XML_FORMAT).toContain(""); + it("should escape XML special characters", () => { + const spec: SpecOutput = { + project_name: "Test & Project", + overview: "Description with ", + technology_stack: ["TypeScript"], + core_capabilities: ["Cap"], + implemented_features: [], + }; + + const xml = specToXml(spec); + + expect(xml).toContain("Test & Project"); + expect(xml).toContain("<tags>"); }); - it("should contain XML escaping instructions", () => { - expect(APP_SPEC_XML_FORMAT).toContain("<"); - expect(APP_SPEC_XML_FORMAT).toContain(">"); - expect(APP_SPEC_XML_FORMAT).toContain("&"); + it("should include file_locations when provided", () => { + const spec: SpecOutput = { + project_name: "Test", + overview: "Test", + technology_stack: ["TS"], + core_capabilities: ["Cap"], + implemented_features: [ + { + name: "Feature", + description: "Desc", + file_locations: ["src/index.ts"], + }, + ], + }; + + const xml = specToXml(spec); + + expect(xml).toContain(""); + expect(xml).toContain("src/index.ts"); + }); + + it("should not include file_locations when empty", () => { + const spec: SpecOutput = { + project_name: "Test", + overview: "Test", + technology_stack: ["TS"], + core_capabilities: ["Cap"], + implemented_features: [ + { name: "Feature", description: "Desc", file_locations: [] }, + ], + }; + + const xml = specToXml(spec); + + expect(xml).not.toContain(""); + }); + + it("should include additional_requirements when provided", () => { + const spec: SpecOutput = { + project_name: "Test", + overview: "Test", + technology_stack: ["TS"], + core_capabilities: ["Cap"], + implemented_features: [], + additional_requirements: ["Node.js 18+"], + }; + + const xml = specToXml(spec); + + expect(xml).toContain(""); + expect(xml).toContain("Node.js 18+"); + }); + + it("should include development_guidelines when provided", () => { + const spec: SpecOutput = { + project_name: "Test", + overview: "Test", + technology_stack: ["TS"], + core_capabilities: ["Cap"], + implemented_features: [], + development_guidelines: ["Use ESLint"], + }; + + const xml = specToXml(spec); + + expect(xml).toContain(""); + expect(xml).toContain("Use ESLint"); + }); + + it("should include implementation_roadmap when provided", () => { + const spec: SpecOutput = { + project_name: "Test", + overview: "Test", + technology_stack: ["TS"], + core_capabilities: ["Cap"], + implemented_features: [], + implementation_roadmap: [ + { phase: "Phase 1", status: "completed", description: "Setup" }, + ], + }; + + const xml = specToXml(spec); + + expect(xml).toContain(""); + expect(xml).toContain("completed"); + }); + + it("should not include optional sections when empty", () => { + const spec: SpecOutput = { + project_name: "Test", + overview: "Test", + technology_stack: ["TS"], + core_capabilities: ["Cap"], + implemented_features: [], + additional_requirements: [], + development_guidelines: [], + implementation_roadmap: [], + }; + + const xml = specToXml(spec); + + expect(xml).not.toContain(""); + expect(xml).not.toContain(""); + expect(xml).not.toContain(""); + }); + }); + + describe("getStructuredSpecPromptInstruction", () => { + it("should return non-empty prompt instruction", () => { + const instruction = getStructuredSpecPromptInstruction(); + expect(instruction).toBeTruthy(); + expect(instruction.length).toBeGreaterThan(100); + }); + + it("should mention required fields", () => { + const instruction = getStructuredSpecPromptInstruction(); + expect(instruction).toContain("project_name"); + expect(instruction).toContain("overview"); + expect(instruction).toContain("technology_stack"); }); }); describe("getAppSpecFormatInstruction", () => { - it("should return a string containing the XML format", () => { + it("should return non-empty format instruction", () => { const instruction = getAppSpecFormatInstruction(); - expect(typeof instruction).toBe("string"); - expect(instruction).toContain(APP_SPEC_XML_FORMAT); + expect(instruction).toBeTruthy(); + expect(instruction.length).toBeGreaterThan(100); }); - it("should contain critical formatting requirements", () => { + it("should include critical formatting requirements", () => { const instruction = getAppSpecFormatInstruction(); expect(instruction).toContain("CRITICAL FORMATTING REQUIREMENTS"); - expect(instruction).toContain(""); - expect(instruction).toContain(""); }); + }); - it("should contain verification instructions", () => { - const instruction = getAppSpecFormatInstruction(); - expect(instruction).toContain("VERIFICATION"); - expect(instruction).toContain("exactly one root XML element"); - }); - - it("should instruct not to use markdown", () => { - const instruction = getAppSpecFormatInstruction(); - expect(instruction).toContain("Do NOT use markdown"); - expect(instruction).toContain("no # headers"); - expect(instruction).toContain("no **bold**"); + describe("APP_SPEC_XML_FORMAT", () => { + it("should contain valid XML template structure", () => { + expect(APP_SPEC_XML_FORMAT).toContain(""); + expect(APP_SPEC_XML_FORMAT).toContain(""); }); }); }); diff --git a/apps/server/tests/unit/lib/automaker-paths.test.ts b/apps/server/tests/unit/lib/automaker-paths.test.ts index e8720663..10797eb8 100644 --- a/apps/server/tests/unit/lib/automaker-paths.test.ts +++ b/apps/server/tests/unit/lib/automaker-paths.test.ts @@ -16,16 +16,19 @@ import { } from "@/lib/automaker-paths.js"; describe("automaker-paths.ts", () => { - const projectPath = "/test/project"; + const projectPath = path.join("/test", "project"); describe("getAutomakerDir", () => { it("should return path to .automaker directory", () => { - expect(getAutomakerDir(projectPath)).toBe("/test/project/.automaker"); + expect(getAutomakerDir(projectPath)).toBe( + path.join(projectPath, ".automaker") + ); }); it("should handle paths with trailing slashes", () => { - expect(getAutomakerDir("/test/project/")).toBe( - path.join("/test/project/", ".automaker") + const pathWithSlash = path.join("/test", "project") + path.sep; + expect(getAutomakerDir(pathWithSlash)).toBe( + path.join(pathWithSlash, ".automaker") ); }); }); @@ -33,7 +36,7 @@ describe("automaker-paths.ts", () => { describe("getFeaturesDir", () => { it("should return path to features directory", () => { expect(getFeaturesDir(projectPath)).toBe( - "/test/project/.automaker/features" + path.join(projectPath, ".automaker", "features") ); }); }); @@ -41,13 +44,13 @@ describe("automaker-paths.ts", () => { describe("getFeatureDir", () => { it("should return path to specific feature directory", () => { expect(getFeatureDir(projectPath, "feature-123")).toBe( - "/test/project/.automaker/features/feature-123" + path.join(projectPath, ".automaker", "features", "feature-123") ); }); it("should handle feature IDs with special characters", () => { expect(getFeatureDir(projectPath, "my-feature_v2")).toBe( - "/test/project/.automaker/features/my-feature_v2" + path.join(projectPath, ".automaker", "features", "my-feature_v2") ); }); }); @@ -55,27 +58,31 @@ describe("automaker-paths.ts", () => { describe("getFeatureImagesDir", () => { it("should return path to feature images directory", () => { expect(getFeatureImagesDir(projectPath, "feature-123")).toBe( - "/test/project/.automaker/features/feature-123/images" + path.join(projectPath, ".automaker", "features", "feature-123", "images") ); }); }); describe("getBoardDir", () => { it("should return path to board directory", () => { - expect(getBoardDir(projectPath)).toBe("/test/project/.automaker/board"); + expect(getBoardDir(projectPath)).toBe( + path.join(projectPath, ".automaker", "board") + ); }); }); describe("getImagesDir", () => { it("should return path to images directory", () => { - expect(getImagesDir(projectPath)).toBe("/test/project/.automaker/images"); + expect(getImagesDir(projectPath)).toBe( + path.join(projectPath, ".automaker", "images") + ); }); }); describe("getWorktreesDir", () => { it("should return path to worktrees directory", () => { expect(getWorktreesDir(projectPath)).toBe( - "/test/project/.automaker/worktrees" + path.join(projectPath, ".automaker", "worktrees") ); }); }); @@ -83,7 +90,7 @@ describe("automaker-paths.ts", () => { describe("getAppSpecPath", () => { it("should return path to app_spec.txt file", () => { expect(getAppSpecPath(projectPath)).toBe( - "/test/project/.automaker/app_spec.txt" + path.join(projectPath, ".automaker", "app_spec.txt") ); }); }); @@ -91,7 +98,7 @@ describe("automaker-paths.ts", () => { describe("getBranchTrackingPath", () => { it("should return path to active-branches.json file", () => { expect(getBranchTrackingPath(projectPath)).toBe( - "/test/project/.automaker/active-branches.json" + path.join(projectPath, ".automaker", "active-branches.json") ); }); });