| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- /**
- * Unit tests for TranslationEngine
- *
- * Tests the orchestration of all mappers for complete agent translation.
- */
- import { describe, it, expect, beforeEach } from "vitest";
- import {
- TranslationEngine,
- createTranslationEngine,
- translate,
- previewTranslation,
- type TranslationTarget,
- type TranslationOptions,
- } from "../../../src/core/TranslationEngine";
- import type { OpenAgent, ToolAccess, GranularPermission } from "../../../src/types";
- // =============================================================================
- // Test Fixtures
- // =============================================================================
- const createTestAgent = (overrides: Partial<OpenAgent> = {}): OpenAgent => ({
- frontmatter: {
- name: "Test Agent",
- description: "A comprehensive test agent",
- mode: "primary",
- model: "claude-sonnet-4",
- temperature: 0.7,
- tools: {
- bash: true,
- read: true,
- write: true,
- edit: true,
- grep: true,
- glob: true,
- task: true,
- },
- permission: {
- bash: { allow: ["npm *", "git *"], deny: ["rm -rf *"] },
- read: { allow: ["**/*"] },
- write: { allow: ["src/**/*"], deny: ["node_modules/**"] },
- },
- skills: ["coding", "testing"],
- ...overrides.frontmatter,
- },
- systemPrompt: "You are a helpful coding assistant.",
- contexts: [
- { path: ".opencode/context/core/standards.md", priority: "critical" },
- { path: ".opencode/context/guides/workflow.md", priority: "high" },
- ],
- ...overrides,
- });
- const createMinimalAgent = (overrides: Partial<OpenAgent> = {}): OpenAgent => ({
- frontmatter: {
- name: "Minimal Agent",
- description: "A minimal test agent",
- mode: "primary",
- ...overrides.frontmatter,
- },
- systemPrompt: "You are a minimal agent.",
- contexts: [],
- ...overrides,
- });
- describe("TranslationEngine", () => {
- let engine: TranslationEngine;
- beforeEach(() => {
- engine = new TranslationEngine();
- });
- // ==========================================================================
- // Constructor and Options
- // ==========================================================================
- describe("constructor", () => {
- it("creates engine with default options", () => {
- const engine = new TranslationEngine();
- const opts = engine.getOptions();
- expect(opts.permissionStrategy).toBe("permissive");
- expect(opts.analyzeCompatibility).toBe(true);
- expect(opts.preserveAsComments).toBe(false);
- expect(opts.modelFallback).toBe("claude-sonnet-4");
- });
- it("creates engine with custom options", () => {
- const engine = new TranslationEngine({
- permissionStrategy: "restrictive",
- analyzeCompatibility: false,
- });
- const opts = engine.getOptions();
- expect(opts.permissionStrategy).toBe("restrictive");
- expect(opts.analyzeCompatibility).toBe(false);
- });
- });
- describe("getOptions()", () => {
- it("returns a copy of options", () => {
- const opts1 = engine.getOptions();
- const opts2 = engine.getOptions();
- expect(opts1).not.toBe(opts2);
- expect(opts1).toEqual(opts2);
- });
- });
- describe("setOptions()", () => {
- it("updates options", () => {
- engine.setOptions({ permissionStrategy: "restrictive" });
- const opts = engine.getOptions();
- expect(opts.permissionStrategy).toBe("restrictive");
- });
- it("preserves unset options", () => {
- engine.setOptions({ permissionStrategy: "restrictive" });
- const opts = engine.getOptions();
- expect(opts.analyzeCompatibility).toBe(true);
- });
- });
- // ==========================================================================
- // translate() - OAC to Platform
- // ==========================================================================
- describe("translate()", () => {
- describe("basic translation", () => {
- it("translates minimal agent successfully", () => {
- const agent = createMinimalAgent();
- const result = engine.translate(agent, "claude");
- expect(result.success).toBe(true);
- expect(result.frontmatter.name).toBe("Minimal Agent");
- expect(result.frontmatter.description).toBe("A minimal test agent");
- });
- it("includes compatibility analysis by default", () => {
- const agent = createMinimalAgent();
- const result = engine.translate(agent, "claude");
- expect(result.compatibility).toBeDefined();
- expect(result.compatibility?.compatible).toBe(true);
- });
- it("skips compatibility analysis when disabled", () => {
- const agent = createMinimalAgent();
- const result = engine.translate(agent, "claude", {
- analyzeCompatibility: false,
- });
- expect(result.compatibility).toBeUndefined();
- });
- });
- describe("tool translation", () => {
- it("translates tools to target platform format", () => {
- const agent = createTestAgent();
- const result = engine.translate(agent, "claude");
- expect(result.tools).toBeDefined();
- expect(result.tools?.Bash).toBe(true);
- expect(result.tools?.Read).toBe(true);
- expect(result.tools?.Write).toBe(true);
- });
- it("translates tools to Cursor format", () => {
- const agent = createTestAgent();
- const result = engine.translate(agent, "cursor");
- expect(result.tools).toBeDefined();
- expect(result.tools?.terminal).toBe(true);
- expect(result.tools?.file_read).toBe(true);
- });
- });
- describe("permission translation", () => {
- it("translates granular permissions to binary format", () => {
- const agent = createTestAgent();
- const result = engine.translate(agent, "claude");
- expect(result.permissions).toBeDefined();
- expect(typeof result.permissions?.bash).toBe("boolean");
- expect(typeof result.permissions?.read).toBe("boolean");
- expect(typeof result.permissions?.write).toBe("boolean");
- });
- it("uses permissive strategy by default", () => {
- const agent = createTestAgent();
- const result = engine.translate(agent, "claude");
- // With permissive strategy, allow patterns enable the permission
- expect(result.permissions?.bash).toBe(true);
- });
- it("respects restrictive strategy option", () => {
- const agent = createTestAgent();
- const result = engine.translate(agent, "claude", {
- permissionStrategy: "restrictive",
- });
- // Even with restrictive, basic permissions should be enabled if allow exists
- expect(result.permissions).toBeDefined();
- });
- });
- describe("model translation", () => {
- it("translates model ID to target platform format", () => {
- const agent = createTestAgent();
- const result = engine.translate(agent, "claude");
- expect(result.model).toBeDefined();
- expect(result.model).toBe("claude-sonnet-4-20250514");
- });
- it("handles missing model", () => {
- const agent = createMinimalAgent();
- const result = engine.translate(agent, "claude");
- expect(result.model).toBeUndefined();
- });
- });
- describe("context translation", () => {
- it("translates context paths to target platform", () => {
- const agent = createTestAgent();
- const result = engine.translate(agent, "claude");
- expect(result.contextPaths).toBeDefined();
- expect(result.contextPaths?.length).toBe(2);
- expect(result.contextPaths?.[0]).toContain(".claude/skills");
- });
- it("returns empty paths for Cursor (inline-only)", () => {
- const agent = createTestAgent();
- const result = engine.translate(agent, "cursor");
- // Cursor doesn't support external context, paths should be filtered
- expect(result.contextPaths).toBeDefined();
- expect(result.contextPaths?.length).toBe(0);
- });
- it("collects context priority warnings", () => {
- const agent = createTestAgent();
- const result = engine.translate(agent, "claude");
- expect(result.warnings.some((w) => w.includes("priority"))).toBe(true);
- });
- });
- describe("skill translation", () => {
- it("translates skills to Claude format", () => {
- const agent = createTestAgent();
- const result = engine.translate(agent, "claude");
- expect(result.skills).toBeDefined();
- expect(result.skills?.length).toBe(2);
- expect(result.skills?.[0]).toBe(".claude/skills/coding.md");
- });
- it("does not include skills for non-Claude targets", () => {
- const agent = createTestAgent();
- const result = engine.translate(agent, "cursor");
- expect(result.skills).toBeUndefined();
- });
- });
- describe("temperature handling", () => {
- it("includes temperature when supported", () => {
- const agent = createTestAgent();
- const result = engine.translate(agent, "cursor");
- // Cursor has partial temperature support
- expect(result.frontmatter.temperature).toBeDefined();
- });
- it("excludes temperature when not supported", () => {
- const agent = createTestAgent();
- const result = engine.translate(agent, "claude");
- // Claude doesn't support temperature
- expect(result.frontmatter.temperature).toBeUndefined();
- });
- });
- describe("warnings collection", () => {
- it("collects all warnings from sub-mappers", () => {
- const agent = createTestAgent();
- const result = engine.translate(agent, "cursor");
- expect(result.warnings.length).toBeGreaterThan(0);
- });
- it("includes compatibility warnings", () => {
- const agent = createTestAgent({
- frontmatter: {
- name: "Agent with hooks",
- description: "Agent",
- mode: "primary",
- hooks: [{ event: "PreToolUse", command: "echo test" }],
- },
- });
- const result = engine.translate(agent, "cursor");
- expect(result.success).toBe(false);
- expect(result.compatibility?.compatible).toBe(false);
- expect(result.compatibility?.blockers.length).toBeGreaterThan(0);
- });
- });
- });
- // ==========================================================================
- // translateToOAC() - Platform to OAC
- // ==========================================================================
- describe("translateToOAC()", () => {
- it("translates Claude format to OAC", () => {
- const source = {
- name: "Claude Agent",
- description: "An agent from Claude",
- tools: { Bash: true, Read: true, Write: true },
- permissions: { bash: true, read: true, write: false },
- model: "claude-sonnet-4-20250514",
- skills: [".claude/skills/coding.md"],
- systemPrompt: "You are helpful.",
- };
- const result = engine.translateToOAC(source, "claude");
- expect(result.success).toBe(true);
- expect(result.agent.frontmatter?.name).toBe("Claude Agent");
- expect(result.agent.frontmatter?.tools).toBeDefined();
- expect(result.agent.frontmatter?.model).toBe("claude-sonnet-4");
- });
- it("translates Cursor format to OAC", () => {
- const source = {
- name: "Cursor Agent",
- tools: { terminal: true, file_read: true },
- permissions: { bash: true, read: true },
- };
- const result = engine.translateToOAC(source, "cursor");
- expect(result.success).toBe(true);
- expect(result.agent.frontmatter?.name).toBe("Cursor Agent");
- expect(result.agent.frontmatter?.tools?.bash).toBe(true);
- expect(result.agent.frontmatter?.tools?.read).toBe(true);
- });
- it("handles missing fields with defaults", () => {
- const source = {};
- const result = engine.translateToOAC(source, "claude");
- expect(result.success).toBe(true);
- expect(result.agent.frontmatter?.name).toBe("Unnamed Agent");
- expect(result.agent.frontmatter?.description).toBe("Imported agent");
- expect(result.agent.frontmatter?.mode).toBe("primary");
- });
- it("translates context paths back to OAC format", () => {
- const source = {
- name: "Agent",
- contextPaths: [".claude/skills/core/standards.md"],
- };
- const result = engine.translateToOAC(source, "claude");
- expect(result.agent.contexts).toBeDefined();
- expect(result.agent.contexts?.[0].path).toBe(
- ".opencode/context/core/standards.md"
- );
- });
- it("converts Claude skills to OAC skill references", () => {
- const source = {
- name: "Agent",
- skills: [".claude/skills/coding.md", ".claude/skills/testing.md"],
- };
- const result = engine.translateToOAC(source, "claude");
- expect(result.agent.frontmatter?.skills).toHaveLength(2);
- expect(result.agent.frontmatter?.skills?.[0]).toBe("coding");
- });
- it("includes system prompt when provided", () => {
- const source = {
- name: "Agent",
- systemPrompt: "Custom prompt",
- };
- const result = engine.translateToOAC(source, "claude");
- expect(result.agent.systemPrompt).toBe("Custom prompt");
- });
- });
- // ==========================================================================
- // translateBatch()
- // ==========================================================================
- describe("translateBatch()", () => {
- it("translates multiple agents", () => {
- const agents = [
- createMinimalAgent({ frontmatter: { name: "Agent 1", description: "First", mode: "primary" } }),
- createMinimalAgent({ frontmatter: { name: "Agent 2", description: "Second", mode: "primary" } }),
- ];
- const results = engine.translateBatch(agents, "claude");
- expect(results).toHaveLength(2);
- expect(results[0].frontmatter.name).toBe("Agent 1");
- expect(results[1].frontmatter.name).toBe("Agent 2");
- });
- it("handles empty array", () => {
- const results = engine.translateBatch([], "claude");
- expect(results).toHaveLength(0);
- });
- it("processes each agent independently", () => {
- const agents = [
- createTestAgent(),
- createMinimalAgent(),
- ];
- const results = engine.translateBatch(agents, "claude");
- expect(results[0].tools).toBeDefined();
- expect(results[1].tools).toBeUndefined();
- });
- });
- // ==========================================================================
- // preview()
- // ==========================================================================
- describe("preview()", () => {
- it("returns compatibility analysis without translation", () => {
- const agent = createTestAgent();
- const result = engine.preview(agent, "cursor");
- expect(result).toHaveProperty("compatible");
- expect(result).toHaveProperty("score");
- expect(result).toHaveProperty("warnings");
- expect(result).toHaveProperty("blockers");
- });
- it("identifies incompatible agents", () => {
- const agent = createTestAgent({
- frontmatter: {
- name: "Agent with hooks",
- description: "Agent",
- mode: "primary",
- hooks: [{ event: "PreToolUse", command: "echo test" }],
- },
- });
- const result = engine.preview(agent, "cursor");
- expect(result.compatible).toBe(false);
- expect(result.blockers.length).toBeGreaterThan(0);
- });
- });
- // ==========================================================================
- // isFullyCompatible()
- // ==========================================================================
- describe("isFullyCompatible()", () => {
- it("returns true for minimal agent to Claude", () => {
- const agent = createMinimalAgent();
- expect(engine.isFullyCompatible(agent, "claude")).toBe(true);
- });
- it("returns false when features will be lost", () => {
- const agent = createTestAgent(); // Has temperature, contexts with priority
- expect(engine.isFullyCompatible(agent, "cursor")).toBe(false);
- });
- });
- // ==========================================================================
- // Factory Functions
- // ==========================================================================
- describe("createTranslationEngine()", () => {
- it("creates engine with default options", () => {
- const engine = createTranslationEngine();
- expect(engine).toBeInstanceOf(TranslationEngine);
- expect(engine.getOptions().permissionStrategy).toBe("permissive");
- });
- it("creates engine with custom options", () => {
- const engine = createTranslationEngine({
- permissionStrategy: "restrictive",
- });
- expect(engine.getOptions().permissionStrategy).toBe("restrictive");
- });
- });
- describe("translate() function", () => {
- it("performs one-off translation", () => {
- const agent = createMinimalAgent();
- const result = translate(agent, "claude");
- expect(result.success).toBe(true);
- expect(result.frontmatter.name).toBe("Minimal Agent");
- });
- it("accepts options", () => {
- const agent = createMinimalAgent();
- const result = translate(agent, "claude", {
- analyzeCompatibility: false,
- });
- expect(result.compatibility).toBeUndefined();
- });
- });
- describe("previewTranslation()", () => {
- it("performs one-off compatibility check", () => {
- const agent = createTestAgent();
- const result = previewTranslation(agent, "cursor");
- expect(result).toHaveProperty("compatible");
- expect(result).toHaveProperty("score");
- });
- });
- // ==========================================================================
- // Edge Cases
- // ==========================================================================
- describe("edge cases", () => {
- it("handles agent with no tools", () => {
- const agent = createMinimalAgent();
- const result = engine.translate(agent, "claude");
- expect(result.success).toBe(true);
- expect(result.tools).toBeUndefined();
- });
- it("handles agent with no permissions", () => {
- const agent = createMinimalAgent();
- const result = engine.translate(agent, "claude");
- expect(result.success).toBe(true);
- expect(result.permissions).toBeUndefined();
- });
- it("handles agent with no contexts", () => {
- const agent = createMinimalAgent({ contexts: [] });
- const result = engine.translate(agent, "claude");
- expect(result.success).toBe(true);
- expect(result.contextPaths).toBeUndefined();
- });
- it("handles agent with no skills", () => {
- const agent = createMinimalAgent();
- const result = engine.translate(agent, "claude");
- expect(result.success).toBe(true);
- expect(result.skills).toBeUndefined();
- });
- it("handles Windsurf target", () => {
- const agent = createTestAgent();
- const result = engine.translate(agent, "windsurf");
- expect(result.success).toBe(true);
- expect(result.frontmatter.name).toBe("Test Agent");
- });
- });
- });
|