diff --git a/packages/cli/__tests__/commands/init.test.ts b/packages/cli/__tests__/commands/init.test.ts index 7f530fd38..7c91c57d4 100644 --- a/packages/cli/__tests__/commands/init.test.ts +++ b/packages/cli/__tests__/commands/init.test.ts @@ -2,9 +2,10 @@ import { describe, it, expect, vi, afterEach } from "vitest"; import { mkdtempSync, writeFileSync, readFileSync, rmSync, existsSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import { createServer } from "node:net"; +import { createServer, type Server } from "node:net"; import { Command } from "commander"; +import { parse as yamlParse } from "yaml"; import { registerInit } from "../../src/commands/init.js"; let tmpDir: string; @@ -85,4 +86,101 @@ describe("init command", () => { await new Promise((resolve) => blocker.close(() => resolve())); } }); + + it("auto mode writes name and sessionPrefix to project config", async () => { + tmpDir = mkdtempSync(join(tmpdir(), "ao-init-test-")); + const outputPath = join(tmpDir, "agent-orchestrator.yaml"); + + const program = new Command(); + program.exitOverride(); + registerInit(program); + + vi.spyOn(console, "log").mockImplementation(() => {}); + + await program.parseAsync(["node", "test", "init", "--auto", "--output", outputPath]); + + const content = readFileSync(outputPath, "utf-8"); + const config = yamlParse(content) as Record; + const projects = config.projects as Record>; + const projectIds = Object.keys(projects); + expect(projectIds.length).toBeGreaterThan(0); + + const projectId = projectIds[0]; + const project = projects[projectId]; + + // BUG-01: sessionPrefix must exist and not be "undefined" + expect(project.sessionPrefix).toBeDefined(); + expect(project.sessionPrefix).not.toBe("undefined"); + expect(typeof project.sessionPrefix).toBe("string"); + expect((project.sessionPrefix as string).length).toBeGreaterThan(0); + + // BUG-02: name must match the project ID + expect(project.name).toBe(projectId); + }); + + it("auto mode warns when no free port is found", async () => { + tmpDir = mkdtempSync(join(tmpdir(), "ao-init-test-")); + const outputPath = join(tmpDir, "agent-orchestrator.yaml"); + + // Mock isPortAvailable to always return false (all ports busy) + const webDirModule = await import("../../src/lib/web-dir.js"); + vi.spyOn(webDirModule, "isPortAvailable").mockResolvedValue(false); + + const logSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + + const program = new Command(); + program.exitOverride(); + registerInit(program); + + await program.parseAsync(["node", "test", "init", "--auto", "--output", outputPath]); + + // Should warn about no free port + const logCalls = logSpy.mock.calls.map((args) => args.join(" ")); + const hasPortWarning = logCalls.some((msg) => msg.includes("No free port found")); + expect(hasPortWarning).toBe(true); + + // Should still write config with fallback port 3000 + const content = readFileSync(outputPath, "utf-8"); + expect(content).toContain("port: 3000"); + }); + + it("--smart flag description includes 'coming soon'", () => { + const program = new Command(); + registerInit(program); + + const initCmd = program.commands.find((c) => c.name() === "init"); + expect(initCmd).toBeDefined(); + + const smartOption = initCmd!.options.find((o) => o.long === "--smart"); + expect(smartOption).toBeDefined(); + expect(smartOption!.description).toContain("coming soon"); + }); + + it("auto mode sessionPrefix uses core generateSessionPrefix heuristics", async () => { + tmpDir = mkdtempSync(join(tmpdir(), "ao-init-test-")); + const outputPath = join(tmpDir, "agent-orchestrator.yaml"); + + // Run from a directory whose basename is kebab-case + // The cwd() in init.ts determines the projectId via basename() + // We can't easily control cwd in tests, but we can verify the prefix + // is consistent with generateSessionPrefix for whatever projectId is used + const { generateSessionPrefix } = await import("@composio/ao-core"); + + const program = new Command(); + program.exitOverride(); + registerInit(program); + + vi.spyOn(console, "log").mockImplementation(() => {}); + + await program.parseAsync(["node", "test", "init", "--auto", "--output", outputPath]); + + const content = readFileSync(outputPath, "utf-8"); + const config = yamlParse(content) as Record; + const projects = config.projects as Record>; + const projectId = Object.keys(projects)[0]; + const project = projects[projectId]; + + // sessionPrefix must match what generateSessionPrefix produces + expect(project.sessionPrefix).toBe(generateSessionPrefix(projectId)); + }); });