diff --git a/e2e/tests/create-agent-pipeline.spec.ts b/e2e/tests/create-agent-pipeline.spec.ts new file mode 100644 index 0000000..d02c80b --- /dev/null +++ b/e2e/tests/create-agent-pipeline.spec.ts @@ -0,0 +1,63 @@ +import { test, expect } from "@playwright/test"; +import * as fs from "fs"; +import * as path from "path"; + +const REPO_ROOT = path.resolve(__dirname, "../.."); +const AGENT_DIR = path.join(REPO_ROOT, "agents/test-personality"); +const LAUNCHER = path.join(REPO_ROOT, "cmd/launcher/main.go"); + +test.describe("create-agent pipeline (validacion estructural)", () => { + test("agents/test-personality/agent.go existe y exporta Rules()", () => { + const agentGo = path.join(AGENT_DIR, "agent.go"); + expect(fs.existsSync(agentGo)).toBe(true); + + const content = fs.readFileSync(agentGo, "utf-8"); + expect(content).toContain("func Rules()"); + expect(content).toContain('agents.Register("test-personality"'); + // Agent (not robot) should have actual rules, not nil + expect(content).toContain("ActionKindLLM"); + }); + + test("agents/test-personality/config.yaml tiene type: agent (default)", () => { + const configYaml = path.join(AGENT_DIR, "config.yaml"); + expect(fs.existsSync(configYaml)).toBe(true); + + const content = fs.readFileSync(configYaml, "utf-8"); + expect(content).toMatch(/id:\s*test-personality/); + expect(content).toMatch(/enabled:\s*true/); + // Should NOT have type: robot + expect(content).not.toMatch(/type:\s*robot/); + }); + + test("agents/test-personality/prompts/system.md existe con personalidad", () => { + const systemPrompt = path.join(AGENT_DIR, "prompts/system.md"); + expect(fs.existsSync(systemPrompt)).toBe(true); + + const content = fs.readFileSync(systemPrompt, "utf-8"); + // Pirate space personality keywords + expect(content.toLowerCase()).toContain("pirata"); + expect(content.toLowerCase()).toContain("cosmonauta"); + expect(content.toLowerCase()).toContain("estelar"); + // Security section + expect(content.toLowerCase()).toContain("seguridad"); + expect(content).toContain("instrucciones obligatorias"); + }); + + test("config.yaml tiene LLM configurado (openai/gpt-4o)", () => { + const configYaml = path.join(AGENT_DIR, "config.yaml"); + const content = fs.readFileSync(configYaml, "utf-8"); + expect(content).toMatch(/provider:\s*openai/); + expect(content).toMatch(/model:\s*"?gpt-4o"?/); + }); + + test("config.yaml tiene encryption habilitada", () => { + const configYaml = path.join(AGENT_DIR, "config.yaml"); + const content = fs.readFileSync(configYaml, "utf-8"); + expect(content).toMatch(/encryption:[\s\S]*?enabled:\s*true/); + }); + + test("cmd/launcher/main.go tiene import de test-personality", () => { + const content = fs.readFileSync(LAUNCHER, "utf-8"); + expect(content).toContain("agents/test-personality"); + }); +}); diff --git a/e2e/tests/test-personality.spec.ts b/e2e/tests/test-personality.spec.ts new file mode 100644 index 0000000..65c36ec --- /dev/null +++ b/e2e/tests/test-personality.spec.ts @@ -0,0 +1,99 @@ +import { test, expect, handleElementDialogs } from "../fixtures/persistent-context"; +import { + goToRoom, + sendMessage, + waitForBotReply, + assertNoDecryptionErrors, +} from "../fixtures/matrix-room"; + +// Keywords that indicate pirate-space personality. +// The LLM is non-deterministic, so we check for presence of ANY keyword from the set. +const PIRATE_SPACE_KEYWORDS = [ + "arrr", + "cosmonauta", + "estelar", + "marea", + "nave", + "galaxia", + "estrella", + "pirata", + "capitan", + "nebulosa", + "intergal", + "asteroide", + "meteorito", + "agujero negro", + "tripulacion", + "🏴‍☠️", + "🚀", + "⭐", + "🌌", + "☄️", + "💀", +]; + +function containsPirateKeyword(text: string): boolean { + const lower = text.toLowerCase(); + return PIRATE_SPACE_KEYWORDS.some((kw) => lower.includes(kw.toLowerCase())); +} + +test.describe("test-personality (pirata espacial)", () => { + test.beforeEach(async ({ page }) => { + await page.goto("/"); + await handleElementDialogs(page); + await goToRoom(page, "Test Personality"); + }); + + test("responde a saludo con personalidad pirata espacial", async ({ page }) => { + await sendMessage(page, "Hola, como estas?"); + + const reply = await waitForBotReply(page, { + timeout: 60_000, + sender: "Test Personality", + }); + expect(reply).toBeTruthy(); + expect(reply.length).toBeGreaterThan(20); + expect(containsPirateKeyword(reply)).toBe(true); + }); + + test("personalidad consistente en respuestas serias", async ({ page }) => { + await sendMessage(page, "Que es la fotosintesis? Responde en una frase."); + + const reply = await waitForBotReply(page, { + timeout: 60_000, + sender: "Test Personality", + }); + expect(reply).toBeTruthy(); + expect(reply.length).toBeGreaterThan(20); + // Should contain real content about photosynthesis + expect(reply.toLowerCase()).toMatch(/luz|sol|planta|energia|clorofila|carbon/i); + // And still maintain pirate personality + expect(containsPirateKeyword(reply)).toBe(true); + }); + + test("!help muestra lista de comandos", async ({ page }) => { + await sendMessage(page, "!help"); + + const reply = await waitForBotReply(page, { + timeout: 10_000, + sender: "Test Personality", + }); + expect(reply).toBeTruthy(); + expect(reply.toLowerCase()).toContain("help"); + expect(reply.toLowerCase()).toContain("ping"); + }); + + test("!ping responde", async ({ page }) => { + await sendMessage(page, "!ping"); + + const reply = await waitForBotReply(page, { + timeout: 10_000, + sender: "Test Personality", + }); + expect(reply).toBeTruthy(); + }); + + test("no hay errores de E2EE en el timeline", async ({ page }) => { + await assertNoDecryptionErrors(page); + }); +});