Skip to main content
Glama
CLICommands.test.tsโ€ข11.6 kB
/** * Tests for CLI Commands - TDD: TESTS FIRST */ import { describe, it, expect, beforeEach, afterEach } from "@jest/globals"; import { exec } from "child_process"; import { promisify } from "util"; import fs from "fs"; import path from "path"; import os from "os"; const execAsync = promisify(exec); describe("CLI Commands", () => { let testDir: string; const cliPath = path.join(process.cwd(), "dist/index.js"); beforeEach(() => { // Create temporary test directory testDir = fs.mkdtempSync(path.join(os.tmpdir(), "writers-aid-test-")); }); afterEach(() => { // Clean up test directory if (fs.existsSync(testDir)) { fs.rmSync(testDir, { recursive: true, force: true }); } }); describe("writers-aid init", () => { it("should initialize a new writing project", async () => { const { stdout } = await execAsync(`node ${cliPath} init ${testDir}`); expect(stdout).toContain("Initialized writing project"); expect(fs.existsSync(path.join(testDir, ".writers-aid"))).toBe(true); expect(fs.existsSync(path.join(testDir, ".writers-aid", "manuscript.db"))).toBe(true); }); it("should index existing markdown files", async () => { // Create test markdown files fs.writeFileSync(path.join(testDir, "chapter1.md"), "# Chapter 1\nContent here"); fs.writeFileSync(path.join(testDir, "chapter2.md"), "# Chapter 2\nMore content"); const { stdout } = await execAsync(`node ${cliPath} init ${testDir}`); expect(stdout).toContain("Indexed 2 files"); }); it("should handle empty directory", async () => { const { stdout } = await execAsync(`node ${cliPath} init ${testDir}`); expect(stdout).toContain("Initialized"); expect(stdout).toContain("0 files"); }); }); describe("writers-aid search", () => { beforeEach(async () => { // Setup test project fs.writeFileSync(path.join(testDir, "chapter1.md"), "# Authentication\nUser authentication with OAuth"); fs.writeFileSync(path.join(testDir, "chapter2.md"), "# Database\nPostgreSQL setup"); await execAsync(`node ${cliPath} init ${testDir}`); }); it("should search for content", async () => { const { stdout } = await execAsync(`node ${cliPath} search "authentication" --project ${testDir}`); expect(stdout).toContain("chapter1.md"); expect(stdout).toContain("OAuth"); }); it("should limit results", async () => { const { stdout } = await execAsync(`node ${cliPath} search "chapter" --limit 1 --project ${testDir}`); const lines = stdout.split("\n").filter(l => l.includes(".md")); expect(lines.length).toBeLessThanOrEqual(1); }); it("should handle no results", async () => { const { stdout } = await execAsync(`node ${cliPath} search "nonexistent" --project ${testDir}`); expect(stdout).toContain("No results found"); }); }); describe("writers-aid stats", () => { beforeEach(async () => { fs.writeFileSync(path.join(testDir, "chapter1.md"), "# Chapter 1\n" + "word ".repeat(500)); fs.writeFileSync(path.join(testDir, "chapter2.md"), "# Chapter 2\n" + "word ".repeat(300)); await execAsync(`node ${cliPath} init ${testDir}`); }); it("should show writing statistics", async () => { const { stdout } = await execAsync(`node ${cliPath} stats --project ${testDir}`); expect(stdout).toContain("Total words:"); expect(stdout).toContain("Total files: 2"); }); it("should break down by chapter", async () => { const { stdout } = await execAsync(`node ${cliPath} stats --by-chapter --project ${testDir}`); expect(stdout).toContain("chapter1.md"); expect(stdout).toContain("chapter2.md"); }); }); describe("writers-aid check", () => { beforeEach(async () => { fs.writeFileSync(path.join(testDir, "chapter1.md"), "# Chapter\n### Skipped Level\nBad structure"); await execAsync(`node ${cliPath} init ${testDir}`); }); it("should run all quality checks", async () => { const { stdout } = await execAsync(`node ${cliPath} check --project ${testDir}`); expect(stdout).toContain("Quality Check Results"); }); it("should detect structure issues", async () => { const { stdout } = await execAsync(`node ${cliPath} check --project ${testDir}`); expect(stdout.toLowerCase()).toMatch(/skipped|structure/); }); it("should check specific file", async () => { const { stdout } = await execAsync(`node ${cliPath} check chapter1.md --project ${testDir}`); expect(stdout).toContain("Quality Check"); }); }); describe("writers-aid terminology", () => { beforeEach(async () => { fs.writeFileSync(path.join(testDir, "chapter1.md"), "Use email for contact"); fs.writeFileSync(path.join(testDir, "chapter2.md"), "Send e-mail to support"); await execAsync(`node ${cliPath} init ${testDir}`); }); it("should detect term inconsistencies", async () => { const { stdout } = await execAsync(`node ${cliPath} terminology --project ${testDir}`); expect(stdout.toLowerCase()).toMatch(/terminology|no.*issues/); }); it("should check specific term", async () => { const { stdout } = await execAsync(`node ${cliPath} terminology --term "email" --project ${testDir}`); expect(stdout).toContain("Variants found"); }); }); describe("writers-aid todos", () => { beforeEach(async () => { fs.writeFileSync(path.join(testDir, "chapter1.md"), "# Chapter\n<!-- TODO: Add examples -->\nContent"); fs.writeFileSync(path.join(testDir, "chapter2.md"), "# Chapter 2\n<!-- FIXME: Fix bug -->"); await execAsync(`node ${cliPath} init ${testDir}`); }); it("should list all TODOs", async () => { const { stdout } = await execAsync(`node ${cliPath} todos --project ${testDir}`); expect(stdout).toMatch(/TODO|FIXME/); }); it("should group by priority", async () => { const { stdout } = await execAsync(`node ${cliPath} todos --group-by priority --project ${testDir}`); expect(stdout.toLowerCase()).toMatch(/high|medium/); }); }); describe("writers-aid outline", () => { beforeEach(async () => { const content = "# Book\n## Chapter 1\n### Section 1.1\n## Chapter 2"; fs.writeFileSync(path.join(testDir, "book.md"), content); await execAsync(`node ${cliPath} init ${testDir}`); }); it("should generate outline", async () => { const { stdout } = await execAsync(`node ${cliPath} outline --project ${testDir}`); expect(stdout).toContain("Manuscript Outline"); expect(stdout).toContain("book.md"); }); it("should limit depth", async () => { const { stdout } = await execAsync(`node ${cliPath} outline --depth 2 --project ${testDir}`); expect(stdout).toContain("Manuscript Outline"); }); it("should include word counts", async () => { const { stdout } = await execAsync(`node ${cliPath} outline --with-stats --project ${testDir}`); expect(stdout).toContain("words"); }); }); describe("writers-aid links", () => { beforeEach(async () => { fs.writeFileSync(path.join(testDir, "chapter1.md"), "[Link](chapter2.md)\n[Broken](missing.md)"); fs.writeFileSync(path.join(testDir, "chapter2.md"), "# Chapter 2"); await execAsync(`node ${cliPath} init ${testDir}`); }); it("should check all links", async () => { const { stdout } = await execAsync(`node ${cliPath} links --project ${testDir}`); expect(stdout).toContain("Links checked"); }); it("should show only broken links", async () => { const { stdout } = await execAsync(`node ${cliPath} links --broken-only --project ${testDir}`); expect(stdout.toLowerCase()).toMatch(/missing\.md|broken/); }); }); describe("writers-aid themes", () => { beforeEach(async () => { fs.writeFileSync(path.join(testDir, "chapter1.md"), "authentication security login password".repeat(10)); fs.writeFileSync(path.join(testDir, "chapter2.md"), "database storage query index".repeat(10)); await execAsync(`node ${cliPath} init ${testDir}`); }); it("should extract main themes", async () => { const { stdout } = await execAsync(`node ${cliPath} themes --project ${testDir}`); expect(stdout).toContain("Theme"); }); it("should limit number of themes", async () => { const { stdout } = await execAsync(`node ${cliPath} themes --num 3 --project ${testDir}`); const themeLines = stdout.split("\n").filter(l => l.trim().length > 0); expect(themeLines.length).toBeLessThanOrEqual(10); // Header + 3 themes + formatting }); }); describe("writers-aid find-related", () => { beforeEach(async () => { fs.writeFileSync(path.join(testDir, "auth.md"), "authentication oauth security login"); fs.writeFileSync(path.join(testDir, "security.md"), "security authentication encryption"); fs.writeFileSync(path.join(testDir, "database.md"), "database sql postgres"); await execAsync(`node ${cliPath} init ${testDir}`); }); it("should find related content", async () => { const { stdout } = await execAsync(`node ${cliPath} find-related auth.md --project ${testDir}`); expect(stdout).toMatch(/security\.md|Related|No related/); }); it("should limit results", async () => { const { stdout } = await execAsync(`node ${cliPath} find-related auth.md --limit 1 --project ${testDir}`); const fileLines = stdout.split("\n").filter(l => l.includes(".md")); expect(fileLines.length).toBeLessThanOrEqual(1); }); }); describe("writers-aid structure", () => { beforeEach(async () => { fs.writeFileSync(path.join(testDir, "bad.md"), "# Title\n### Skipped\n##### Too Deep"); await execAsync(`node ${cliPath} init ${testDir}`); }); it("should validate structure", async () => { const { stdout } = await execAsync(`node ${cliPath} structure bad.md --project ${testDir}`); expect(stdout).toMatch(/Structure|issue/); }); it("should validate all files", async () => { const { stdout } = await execAsync(`node ${cliPath} structure --all --project ${testDir}`); expect(stdout.toLowerCase()).toContain("file"); }); }); describe("writers-aid config", () => { it("should show current config", async () => { const { stdout } = await execAsync(`node ${cliPath} config`); expect(stdout.toLowerCase()).toMatch(/configuration|config/); }); it("should get specific value", async () => { const { stdout } = await execAsync(`node ${cliPath} config get provider`); expect(stdout.trim().length).toBeGreaterThan(0); }); }); describe("writers-aid changes", () => { beforeEach(async () => { fs.writeFileSync(path.join(testDir, "chapter1.md"), "# Chapter 1\nOriginal content"); await execAsync(`node ${cliPath} init ${testDir}`); // Make a change await new Promise(resolve => setTimeout(resolve, 1000)); fs.writeFileSync(path.join(testDir, "chapter1.md"), "# Chapter 1\nUpdated content"); }); it("should track changes", async () => { const { stdout } = await execAsync(`node ${cliPath} changes --project ${testDir}`); expect(stdout).toMatch(/Changes|chapter1\.md/); }); it("should filter by time", async () => { const { stdout } = await execAsync(`node ${cliPath} changes --since yesterday --project ${testDir}`); expect(stdout).toMatch(/Change|No changes/); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/xiaolai/claude-writers-aid-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server