Files
stack/apps/api/src/knowledge/utils/markdown.spec.ts
T
Jason WoltjeandClaude Sonnet 4.5 12abdfe81d feat(#93): implement agent spawn via federation
Implements FED-010: Agent Spawn via Federation feature that enables
spawning and managing Claude agents on remote federated Mosaic Stack
instances via COMMAND message type.

Features:
- Federation agent command types (spawn, status, kill)
- FederationAgentService for handling agent operations
- Integration with orchestrator's agent spawner/lifecycle services
- API endpoints for spawning, querying status, and killing agents
- Full command routing through federation COMMAND infrastructure
- Comprehensive test coverage (12/12 tests passing)

Architecture:
- Hub → Spoke: Spawn agents on remote instances
- Command flow: FederationController → FederationAgentService →
  CommandService → Remote Orchestrator
- Response handling: Remote orchestrator returns agent status/results
- Security: Connection validation, signature verification

Files created:
- apps/api/src/federation/types/federation-agent.types.ts
- apps/api/src/federation/federation-agent.service.ts
- apps/api/src/federation/federation-agent.service.spec.ts

Files modified:
- apps/api/src/federation/command.service.ts (agent command routing)
- apps/api/src/federation/federation.controller.ts (agent endpoints)
- apps/api/src/federation/federation.module.ts (service registration)
- apps/orchestrator/src/api/agents/agents.controller.ts (status endpoint)
- apps/orchestrator/src/api/agents/agents.module.ts (lifecycle integration)

Testing:
- 12/12 tests passing for FederationAgentService
- All command service tests passing
- TypeScript compilation successful
- Linting passed

Refs #93

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
2026-02-03 14:37:06 -06:00

349 lines
10 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { renderMarkdown, renderMarkdownSync, markdownToPlainText } from "./markdown";
describe("Markdown Rendering", () => {
describe("renderMarkdown", () => {
it("should render basic markdown to HTML", async () => {
const markdown = "# Hello World\n\nThis is **bold** text.";
const html = await renderMarkdown(markdown);
expect(html).toContain("<h1");
expect(html).toContain("Hello World");
expect(html).toContain("<strong>bold</strong>");
});
it("should handle empty input", async () => {
const html = await renderMarkdown("");
expect(html).toBe("");
});
it("should handle null/undefined input", async () => {
expect(await renderMarkdown(null as any)).toBe("");
expect(await renderMarkdown(undefined as any)).toBe("");
});
it("should sanitize potentially dangerous HTML", async () => {
const markdown = '<script>alert("XSS")</script>\n\n**Safe** content';
const html = await renderMarkdown(markdown);
expect(html).not.toContain("<script>");
expect(html).not.toContain("alert");
expect(html).toContain("<strong>Safe</strong>");
});
it("should sanitize onclick handlers", async () => {
const markdown = '[Click me](javascript:alert("XSS"))';
const html = await renderMarkdown(markdown);
expect(html).not.toContain("javascript:");
expect(html).not.toContain("onclick");
});
});
describe("GFM Features", () => {
it("should render tables", async () => {
const markdown = `
| Header 1 | Header 2 |
|----------|----------|
| Cell 1 | Cell 2 |
| Cell 3 | Cell 4 |
`.trim();
const html = await renderMarkdown(markdown);
expect(html).toContain("<table>");
expect(html).toContain("<thead>");
expect(html).toContain("<tbody>");
expect(html).toContain("<th>Header 1</th>");
expect(html).toContain("<td>Cell 1</td>");
});
it("should render strikethrough text", async () => {
const markdown = "This is ~~deleted~~ text";
const html = await renderMarkdown(markdown);
expect(html).toContain("<del>deleted</del>");
});
it("should render task lists", async () => {
const markdown = `
- [ ] Unchecked task
- [x] Checked task
`.trim();
const html = await renderMarkdown(markdown);
expect(html).toContain("<input");
expect(html).toContain('type="checkbox"');
expect(html).toContain('disabled="disabled"'); // Should be disabled for safety
});
it("should render autolinks", async () => {
const markdown = "Visit https://example.com for more info";
const html = await renderMarkdown(markdown);
expect(html).toContain('<a href="https://example.com"');
});
});
describe("Code Highlighting", () => {
it("should render inline code", async () => {
const markdown = "Use the `console.log()` function";
const html = await renderMarkdown(markdown);
expect(html).toContain("<code>");
expect(html).toContain("console.log()");
});
it("should render code blocks with language", async () => {
const markdown = `
\`\`\`typescript
const greeting: string = "Hello";
console.log(greeting);
\`\`\`
`.trim();
const html = await renderMarkdown(markdown);
expect(html).toContain("<pre>");
expect(html).toContain("<code");
expect(html).toContain("language-typescript");
// Should have syntax highlighting
expect(html.length).toBeGreaterThan(markdown.length);
});
it("should render code blocks without language", async () => {
const markdown = `
\`\`\`
plain text code
\`\`\`
`.trim();
const html = await renderMarkdown(markdown);
expect(html).toContain("<pre>");
expect(html).toContain("<code");
expect(html).toContain("plain text code");
});
});
describe("Links and Images", () => {
it("should render links with target=_blank for external URLs", async () => {
const markdown = "[External Link](https://example.com)";
const html = await renderMarkdown(markdown);
expect(html).toContain('href="https://example.com"');
expect(html).toContain('target="_blank"');
expect(html).toContain('rel="noopener noreferrer"');
});
it("should render images", async () => {
const markdown = "![Alt text](https://example.com/image.png)";
const html = await renderMarkdown(markdown);
expect(html).toContain("<img");
expect(html).toContain('src="https://example.com/image.png"');
expect(html).toContain('alt="Alt text"');
});
it("should allow data URIs for images", async () => {
const markdown =
"![Image](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==)";
const html = await renderMarkdown(markdown);
expect(html).toContain("<img");
expect(html).toContain('src="data:image/png;base64');
});
});
describe("Headers and IDs", () => {
it("should generate IDs for headers", async () => {
const markdown = "# My Header Title";
const html = await renderMarkdown(markdown);
expect(html).toContain("<h1");
expect(html).toContain('id="');
});
it("should render all header levels", async () => {
const markdown = `
# H1
## H2
### H3
#### H4
##### H5
###### H6
`.trim();
const html = await renderMarkdown(markdown);
expect(html).toContain("<h1");
expect(html).toContain("<h2");
expect(html).toContain("<h3");
expect(html).toContain("<h4");
expect(html).toContain("<h5");
expect(html).toContain("<h6");
});
});
describe("Lists", () => {
it("should render unordered lists", async () => {
const markdown = `
- Item 1
- Item 2
- Nested item
`.trim();
const html = await renderMarkdown(markdown);
expect(html).toContain("<ul>");
expect(html).toContain("<li>Item 1</li>");
expect(html).toContain("Nested item");
});
it("should render ordered lists", async () => {
const markdown = `
1. First
2. Second
3. Third
`.trim();
const html = await renderMarkdown(markdown);
expect(html).toContain("<ol>");
expect(html).toContain("<li>First</li>");
expect(html).toContain("<li>Second</li>");
});
});
describe("Quotes and Formatting", () => {
it("should render blockquotes", async () => {
const markdown = "> This is a quote\n> Multi-line quote";
const html = await renderMarkdown(markdown);
expect(html).toContain("<blockquote>");
expect(html).toContain("This is a quote");
});
it("should render emphasis and strong", async () => {
const markdown = "*italic* **bold** ***bold italic***";
const html = await renderMarkdown(markdown);
expect(html).toContain("<em>italic</em>");
expect(html).toContain("<strong>bold</strong>");
});
it("should render horizontal rules", async () => {
const markdown = "Content\n\n---\n\nMore content";
const html = await renderMarkdown(markdown);
expect(html).toContain("<hr");
});
});
describe("renderMarkdownSync", () => {
it("should render markdown synchronously", () => {
const markdown = "# Sync Test\n\n**Bold** text";
const html = renderMarkdownSync(markdown);
expect(html).toContain("<h1");
expect(html).toContain("Sync Test");
expect(html).toContain("<strong>Bold</strong>");
});
it("should handle empty input synchronously", () => {
const html = renderMarkdownSync("");
expect(html).toBe("");
});
it("should sanitize XSS synchronously", () => {
const markdown = '<script>alert("XSS")</script>';
const html = renderMarkdownSync(markdown);
expect(html).not.toContain("<script>");
expect(html).not.toContain("alert");
});
});
describe("markdownToPlainText", () => {
it("should extract plain text from markdown", async () => {
const markdown = "# Header\n\n**Bold** and *italic* text";
const plainText = await markdownToPlainText(markdown);
expect(plainText).toContain("Header");
expect(plainText).toContain("Bold");
expect(plainText).toContain("italic");
expect(plainText).not.toContain("<h1");
expect(plainText).not.toContain("<strong>");
expect(plainText).not.toContain("<em>");
});
it("should strip all HTML tags", async () => {
const markdown = "[Link](https://example.com)\n\n![Image](image.png)";
const plainText = await markdownToPlainText(markdown);
expect(plainText).not.toContain("<a");
expect(plainText).not.toContain("<img");
expect(plainText).toContain("Link");
});
});
describe("Security Tests", () => {
it("should block iframe injection", async () => {
const markdown = '<iframe src="https://evil.com"></iframe>';
const html = await renderMarkdown(markdown);
expect(html).not.toContain("<iframe");
});
it("should block object/embed tags", async () => {
const markdown = '<object data="malware.swf"></object>';
const html = await renderMarkdown(markdown);
expect(html).not.toContain("<object");
});
it("should block event handlers in markdown links", async () => {
const markdown = '[Click](# "onclick=alert(1)")';
const html = await renderMarkdown(markdown);
expect(html).not.toContain("onclick");
});
it("should sanitize SVG with script", async () => {
const markdown = '<svg><script>alert("XSS")</script></svg>';
const html = await renderMarkdown(markdown);
expect(html).not.toContain("<svg");
expect(html).not.toContain("<script>");
});
});
describe("Edge Cases", () => {
it("should handle very long content", async () => {
const markdown = "# Test\n\n" + "A ".repeat(10000);
const html = await renderMarkdown(markdown);
expect(html.length).toBeGreaterThan(0);
expect(html).toContain("<h1");
});
it("should handle unicode characters", async () => {
const markdown = "# 你好 World 🌍\n\n**Émojis**: 😀 ✨ 🚀";
const html = await renderMarkdown(markdown);
expect(html).toContain("你好");
expect(html).toContain("🌍");
expect(html).toContain("😀");
});
it("should handle nested markdown correctly", async () => {
const markdown = "**Bold with _italic_ inside**";
const html = await renderMarkdown(markdown);
expect(html).toContain("<strong>");
expect(html).toContain("<em>");
});
});
});