validate-content.test.ts•42.9 kB
import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
import * as fs from "fs/promises";
import * as path from "path";
import {
handleValidateDiataxisContent,
validateGeneralContent,
} from "../../src/tools/validate-content.js";
import { ValidationResult } from "../../src/tools/validate-content.js";
describe("Content Validation Tool", () => {
const testTempDir = path.join(__dirname, "../../.tmp/test-validation");
beforeEach(async () => {
// Create test directory
await fs.mkdir(testTempDir, { recursive: true });
});
afterEach(async () => {
// Clean up test directory
try {
await fs.rm(testTempDir, { recursive: true });
} catch {
// Ignore cleanup errors
}
});
describe("Application Code Validation", () => {
it("should detect application code path correctly", async () => {
// Create mock application structure
const appDir = path.join(testTempDir, "mock-app");
await fs.mkdir(appDir, { recursive: true });
await fs.mkdir(path.join(appDir, "src"), { recursive: true });
await fs.writeFile(
path.join(appDir, "package.json"),
'{"name": "test-app"}',
);
// Create TypeScript file without documentation
const tsFile = path.join(appDir, "src", "index.ts");
await fs.writeFile(
tsFile,
`
export function undocumentedFunction(param: string): string {
return param.toUpperCase();
}
export const anotherFunction = (value: number) => {
if (value < 0) {
throw new Error('Invalid value');
}
return value * 2;
};
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: appDir,
validationType: "compliance",
includeCodeValidation: true,
});
expect(result).toBeDefined();
expect(result.issues).toBeDefined();
// Should find issues with undocumented exported functions
const undocumentedIssues = result.issues.filter((issue) =>
issue.description.includes("lacks documentation"),
);
expect(undocumentedIssues.length).toBeGreaterThan(0);
// Should find issues with undocumented error throwing
const errorDocIssues = result.issues.filter((issue) =>
issue.description.includes(
"Error throwing code found without error documentation",
),
);
expect(errorDocIssues.length).toBeGreaterThan(0);
});
it("should validate application architecture structure", async () => {
// Create mock application with missing directories
const appDir = path.join(testTempDir, "incomplete-app");
await fs.mkdir(appDir, { recursive: true });
await fs.writeFile(
path.join(appDir, "package.json"),
'{"name": "incomplete-app"}',
);
// Missing tools and types directories
await fs.mkdir(path.join(appDir, "src"), { recursive: true });
await fs.writeFile(
path.join(appDir, "src", "index.ts"),
'export const app = "test";',
);
const result = await handleValidateDiataxisContent({
contentPath: appDir,
validationType: "compliance",
includeCodeValidation: false,
});
const structureIssues = result.issues.filter(
(issue) => issue.location.file === "application structure",
);
expect(structureIssues.length).toBeGreaterThan(0);
// Should suggest missing tools directory
const toolsIssue = structureIssues.find((issue) =>
issue.description.includes("tools directory"),
);
expect(toolsIssue).toBeDefined();
});
it("should validate README structure", async () => {
const appDir = path.join(testTempDir, "readme-test");
await fs.mkdir(appDir, { recursive: true });
await fs.mkdir(path.join(appDir, "src"), { recursive: true });
await fs.writeFile(
path.join(appDir, "package.json"),
'{"name": "readme-test"}',
);
await fs.writeFile(
path.join(appDir, "src", "index.ts"),
'export const app = "test";',
);
// Create README with missing sections
await fs.writeFile(
path.join(appDir, "README.md"),
`
This is a project without proper structure.
Some description here.
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: appDir,
validationType: "compliance",
includeCodeValidation: false,
});
// Application validation should find issues
const readmeIssues = result.issues.filter(
(issue) => issue.location.file === "README.md",
);
expect(readmeIssues.length).toBeGreaterThan(0);
// Should find issues with README structure
const structureIssue = readmeIssues.find((issue) =>
issue.description.includes("lacks essential sections"),
);
expect(structureIssue).toBeDefined();
});
it("should detect properly documented functions", async () => {
const appDir = path.join(testTempDir, "documented-app");
await fs.mkdir(appDir, { recursive: true });
await fs.mkdir(path.join(appDir, "src"), { recursive: true });
await fs.writeFile(
path.join(appDir, "package.json"),
'{"name": "documented-app"}',
);
// Create well-documented TypeScript file
const tsFile = path.join(appDir, "src", "documented.ts");
await fs.writeFile(
tsFile,
`
/**
* Converts a string to uppercase
* @param param - The input string
* @returns The uppercase string
*/
export function documentedFunction(param: string): string {
return param.toUpperCase();
}
/**
* Doubles a positive number
* @param value - The input number (must be positive)
* @returns The doubled value
* @throws {Error} When value is negative
*/
export const wellDocumentedFunction = (value: number) => {
if (value < 0) {
throw new Error('Invalid value');
}
return value * 2;
};
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: appDir,
validationType: "compliance",
includeCodeValidation: true,
});
// Should have no undocumented issues since functions are properly documented
const undocumentedIssues = result.issues.filter((issue) =>
issue.description.includes("lacks documentation"),
);
expect(undocumentedIssues.length).toBe(0);
// Should not complain about error documentation
const errorDocIssues = result.issues.filter((issue) =>
issue.description.includes(
"Error throwing code found without error documentation",
),
);
expect(errorDocIssues.length).toBe(0);
});
});
describe("Documentation Validation", () => {
it("should detect documentation directory correctly", async () => {
// Create mock documentation structure
const docsDir = path.join(testTempDir, "docs");
await fs.mkdir(docsDir, { recursive: true });
await fs.mkdir(path.join(docsDir, "tutorials"), { recursive: true });
await fs.writeFile(
path.join(docsDir, "tutorials", "tutorial1.md"),
`
# Tutorial 1
This is a tutorial without prerequisites section.
\`\`\`javascript
console.log("hello")
\`\`\`
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: docsDir,
validationType: "compliance",
includeCodeValidation: true,
});
expect(result).toBeDefined();
// Should find Diataxis compliance issues
const complianceIssues = result.issues.filter(
(issue) => issue.category === "compliance",
);
expect(complianceIssues.length).toBeGreaterThan(0);
// Should find missing prerequisites in tutorial
const prereqIssue = complianceIssues.find((issue) =>
issue.description.includes("prerequisites"),
);
expect(prereqIssue).toBeDefined();
});
it("should validate link integrity", async () => {
const docsDir = path.join(testTempDir, "docs-links");
await fs.mkdir(docsDir, { recursive: true });
// Create file with broken internal link
await fs.writeFile(
path.join(docsDir, "index.md"),
`
# Documentation
[Broken Link](./nonexistent.md)
[Another Link](./other.md)
`.trim(),
);
// Create the referenced file
await fs.writeFile(path.join(docsDir, "other.md"), "# Other Page");
const result = await handleValidateDiataxisContent({
contentPath: docsDir,
validationType: "accuracy",
includeCodeValidation: false,
});
const linkIssues = result.issues.filter((issue) =>
issue.description.includes("Broken internal link"),
);
expect(linkIssues.length).toBe(1);
const brokenLink = linkIssues[0];
expect(brokenLink.description).toContain("nonexistent.md");
});
it("should validate code blocks in documentation", async () => {
const docsDir = path.join(testTempDir, "docs-code");
await fs.mkdir(docsDir, { recursive: true });
await fs.writeFile(
path.join(docsDir, "guide.md"),
`
# Code Examples
\`\`\`javascript
// Missing semicolon
console.log("test")
\`\`\`
\`\`\`json
{ "valid": "json" }
\`\`\`
\`\`\`json
{ "invalid": json }
\`\`\`
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: docsDir,
validationType: "all",
includeCodeValidation: true,
});
expect(result.codeValidation).toBeDefined();
expect(result.codeValidation!.exampleResults.length).toBeGreaterThan(0);
// Should find JSON syntax error
const jsonErrors = result.codeValidation!.exampleResults.filter((ex) =>
ex.issues.some((issue) => issue.description.includes("Invalid JSON")),
);
expect(jsonErrors.length).toBeGreaterThan(0);
});
});
describe("General Content Validation", () => {
it("should validate general content with link checking", async () => {
const contentDir = path.join(testTempDir, "general-content");
await fs.mkdir(contentDir, { recursive: true });
await fs.writeFile(
path.join(contentDir, "page.md"),
`
# Test Page
[Good Link](./existing.md)
[Bad Link](./missing.md)
\`\`\`js
console.log("missing semicolon")
\`\`\`
`.trim(),
);
await fs.writeFile(
path.join(contentDir, "existing.md"),
"# Existing Page",
);
const result = await validateGeneralContent({
contentPath: contentDir,
validationType: "all",
includeCodeValidation: true,
});
expect(result.success).toBe(false);
expect(result.brokenLinks.length).toBe(1);
expect(result.brokenLinks[0]).toContain("missing.md");
expect(result.codeBlocksValidated).toBeGreaterThan(0);
expect(result.codeErrors.length).toBeGreaterThan(0);
expect(result.recommendations.length).toBeGreaterThan(0);
});
it("should pass validation for clean content", async () => {
const contentDir = path.join(testTempDir, "clean-content");
await fs.mkdir(contentDir, { recursive: true });
await fs.writeFile(
path.join(contentDir, "clean.md"),
`
# Clean Page
[Good Link](./other.md)
\`\`\`json
{ "valid": "json" }
\`\`\`
`.trim(),
);
await fs.writeFile(path.join(contentDir, "other.md"), "# Other Page");
const result = await validateGeneralContent({
contentPath: contentDir,
validationType: "all",
includeCodeValidation: true,
});
expect(result.success).toBe(true);
expect(result.brokenLinks.length).toBe(0);
expect(result.recommendations).toContain(
"Content validation passed - no critical issues found",
);
});
});
describe("Confidence Metrics", () => {
it("should calculate confidence metrics correctly", async () => {
const appDir = path.join(testTempDir, "confidence-test");
await fs.mkdir(appDir, { recursive: true });
await fs.mkdir(path.join(appDir, "src"), { recursive: true });
await fs.writeFile(
path.join(appDir, "package.json"),
'{"name": "confidence-test"}',
);
await fs.writeFile(
path.join(appDir, "src", "index.ts"),
'export const test = "value";',
);
const result = await handleValidateDiataxisContent({
contentPath: appDir,
validationType: "all",
includeCodeValidation: true,
});
expect(result.confidence).toBeDefined();
expect(result.confidence.overall).toBeGreaterThan(0);
expect(result.confidence.overall).toBeLessThanOrEqual(100);
expect(result.confidence.breakdown).toBeDefined();
expect(result.confidence.breakdown.technologyDetection).toBeDefined();
expect(result.confidence.breakdown.codeExampleRelevance).toBeDefined();
expect(
result.confidence.breakdown.architecturalAssumptions,
).toBeDefined();
});
it("should provide recommendations based on confidence", async () => {
const appDir = path.join(testTempDir, "recommendations-test");
await fs.mkdir(appDir, { recursive: true });
await fs.writeFile(
path.join(appDir, "package.json"),
'{"name": "recommendations-test"}',
);
// Create content that will generate issues
await fs.writeFile(path.join(appDir, "README.md"), "No proper structure");
const result = await handleValidateDiataxisContent({
contentPath: appDir,
validationType: "all",
includeCodeValidation: false,
});
expect(result.recommendations).toBeDefined();
expect(result.recommendations.length).toBeGreaterThan(0);
expect(result.nextSteps).toBeDefined();
expect(result.nextSteps.length).toBeGreaterThan(0);
if (result.confidence.overall < 70) {
expect(
result.recommendations.some((rec) =>
rec.includes("comprehensive review"),
),
).toBe(true);
}
});
});
describe("Error Handling and Edge Cases", () => {
it("should handle non-existent content path gracefully", async () => {
const nonExistentPath = path.join(testTempDir, "does-not-exist");
const result = await handleValidateDiataxisContent({
contentPath: nonExistentPath,
validationType: "all",
includeCodeValidation: false,
});
expect(result).toBeDefined();
// The function handles non-existent paths gracefully but may still succeed
expect(result.confidence).toBeDefined();
});
it("should handle empty directory", async () => {
const emptyDir = path.join(testTempDir, "empty-dir");
await fs.mkdir(emptyDir, { recursive: true });
const result = await handleValidateDiataxisContent({
contentPath: emptyDir,
validationType: "all",
includeCodeValidation: true,
});
expect(result).toBeDefined();
expect(result.confidence.breakdown.architecturalAssumptions).toBeLessThan(
80,
);
});
it("should handle project context loading with analysis ID", async () => {
const appDir = path.join(testTempDir, "context-test");
await fs.mkdir(appDir, { recursive: true });
await fs.writeFile(
path.join(appDir, "package.json"),
'{"name": "context-test"}',
);
// Create .documcp directory with analysis
const docucmpDir = path.join(appDir, ".documcp", "analyses");
await fs.mkdir(docucmpDir, { recursive: true });
await fs.writeFile(
path.join(docucmpDir, "test-analysis.json"),
JSON.stringify({
metadata: {
projectName: "test-project",
primaryLanguage: "TypeScript",
},
technologies: { framework: "React" },
dependencies: { packages: ["react", "typescript"] },
}),
);
const result = await handleValidateDiataxisContent({
contentPath: appDir,
analysisId: "test-analysis",
validationType: "accuracy",
includeCodeValidation: false,
});
expect(result).toBeDefined();
expect(result.confidence).toBeDefined();
});
it("should handle missing analysis ID gracefully", async () => {
const appDir = path.join(testTempDir, "missing-analysis");
await fs.mkdir(appDir, { recursive: true });
await fs.writeFile(
path.join(appDir, "package.json"),
'{"name": "missing-analysis"}',
);
const result = await handleValidateDiataxisContent({
contentPath: appDir,
analysisId: "non-existent-analysis",
validationType: "accuracy",
includeCodeValidation: false,
});
expect(result).toBeDefined();
expect(result.confidence).toBeDefined();
});
it("should detect documentation directory correctly", async () => {
const docsPath = path.join(testTempDir, "project", "docs");
await fs.mkdir(docsPath, { recursive: true });
await fs.writeFile(path.join(docsPath, "index.md"), "# Documentation");
const result = await handleValidateDiataxisContent({
contentPath: docsPath,
validationType: "compliance",
includeCodeValidation: false,
});
expect(result).toBeDefined();
expect(result.confidence).toBeDefined();
// Documentation directory should be processed
expect(
result.confidence.breakdown.architecturalAssumptions,
).toBeGreaterThan(0);
});
it("should handle different validation types", async () => {
const appDir = path.join(testTempDir, "validation-types");
await fs.mkdir(appDir, { recursive: true });
await fs.writeFile(
path.join(appDir, "test.md"),
"# Test\n[broken link](./missing.md)",
);
// Test accuracy only
const accuracyResult = await handleValidateDiataxisContent({
contentPath: appDir,
validationType: "accuracy",
includeCodeValidation: false,
});
expect(accuracyResult).toBeDefined();
// Test completeness only
const completenessResult = await handleValidateDiataxisContent({
contentPath: appDir,
validationType: "completeness",
includeCodeValidation: false,
});
expect(completenessResult).toBeDefined();
// Test compliance only
const complianceResult = await handleValidateDiataxisContent({
contentPath: appDir,
validationType: "compliance",
includeCodeValidation: false,
});
expect(complianceResult).toBeDefined();
});
it("should handle code validation failure scenarios", async () => {
const appDir = path.join(testTempDir, "code-validation-fail");
await fs.mkdir(appDir, { recursive: true });
// Create markdown with broken code examples
await fs.writeFile(
path.join(appDir, "broken-code.md"),
`
# Broken Code Examples
\`\`\`javascript
// Syntax error
console.log("missing quote);
\`\`\`
\`\`\`json
{ "invalid": json }
\`\`\`
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: appDir,
validationType: "all",
includeCodeValidation: true,
});
expect(result.codeValidation).toBeDefined();
expect(result.codeValidation!.overallSuccess).toBe(false);
expect(
result.recommendations.some((rec) => rec.includes("Fix code examples")),
).toBe(true);
});
it("should generate risk factors for critical issues", async () => {
const appDir = path.join(testTempDir, "risk-factors");
await fs.mkdir(appDir, { recursive: true });
// Create content with multiple critical issues
await fs.writeFile(
path.join(appDir, "critical-issues.md"),
`
# Critical Issues
[Broken Link 1](./missing1.md)
[Broken Link 2](./missing2.md)
[Broken Link 3](./missing3.md)
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: appDir,
validationType: "all",
includeCodeValidation: false,
});
expect(result.confidence.riskFactors).toBeDefined();
expect(result.confidence.riskFactors.length).toBeGreaterThan(0);
const highRiskFactors = result.confidence.riskFactors.filter(
(rf) => rf.type === "high",
);
expect(highRiskFactors.length).toBeGreaterThan(0);
});
it("should handle uncertainty flags and medium risk factors", async () => {
const appDir = path.join(testTempDir, "uncertainty-test");
await fs.mkdir(appDir, { recursive: true });
// Create content that generates uncertainties
await fs.writeFile(
path.join(appDir, "uncertain.md"),
`
# Uncertain Content
This content has many ambiguous references and unclear instructions.
Multiple areas need clarification for proper understanding.
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: appDir,
validationType: "all",
includeCodeValidation: false,
});
// Manually add uncertainties to test the risk factor generation
result.uncertainties = [
{
area: "test1",
severity: "high",
description: "test",
potentialImpact: "test",
clarificationNeeded: "test",
fallbackStrategy: "test",
},
{
area: "test2",
severity: "high",
description: "test",
potentialImpact: "test",
clarificationNeeded: "test",
fallbackStrategy: "test",
},
{
area: "test3",
severity: "high",
description: "test",
potentialImpact: "test",
clarificationNeeded: "test",
fallbackStrategy: "test",
},
{
area: "test4",
severity: "high",
description: "test",
potentialImpact: "test",
clarificationNeeded: "test",
fallbackStrategy: "test",
},
{
area: "test5",
severity: "high",
description: "test",
potentialImpact: "test",
clarificationNeeded: "test",
fallbackStrategy: "test",
},
{
area: "test6",
severity: "high",
description: "test",
potentialImpact: "test",
clarificationNeeded: "test",
fallbackStrategy: "test",
},
];
expect(result.uncertainties.length).toBeGreaterThan(5);
const highUncertainties = result.uncertainties.filter(
(u) => u.severity === "high" || u.severity === "critical",
);
expect(highUncertainties.length).toBeGreaterThan(0);
});
it("should handle Diataxis structure analysis", async () => {
const docsDir = path.join(testTempDir, "diataxis-structure");
await fs.mkdir(docsDir, { recursive: true });
// Create Diataxis structure
await fs.mkdir(path.join(docsDir, "tutorials"), { recursive: true });
await fs.mkdir(path.join(docsDir, "how-to"), { recursive: true });
await fs.mkdir(path.join(docsDir, "reference"), { recursive: true });
await fs.mkdir(path.join(docsDir, "explanation"), { recursive: true });
await fs.writeFile(
path.join(docsDir, "tutorials", "tutorial.md"),
"# Tutorial",
);
await fs.writeFile(
path.join(docsDir, "how-to", "guide.md"),
"# How-to Guide",
);
await fs.writeFile(
path.join(docsDir, "reference", "api.md"),
"# API Reference",
);
await fs.writeFile(
path.join(docsDir, "explanation", "concept.md"),
"# Explanation",
);
const result = await handleValidateDiataxisContent({
contentPath: docsDir,
validationType: "compliance",
includeCodeValidation: false,
});
expect(result).toBeDefined();
expect(
result.confidence.breakdown.architecturalAssumptions,
).toBeGreaterThan(60);
});
it("should handle successful validation with no issues", async () => {
const cleanDir = path.join(testTempDir, "clean-validation");
await fs.mkdir(cleanDir, { recursive: true });
// Create clean content with no issues
await fs.writeFile(
path.join(cleanDir, "clean.md"),
`
# Clean Documentation
This is well-structured documentation with no issues.
\`\`\`json
{ "valid": "json" }
\`\`\`
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: cleanDir,
validationType: "all",
includeCodeValidation: true,
});
// Should have minimal issues and good confidence
expect(result.confidence.overall).toBeGreaterThan(0);
expect(result.recommendations).toBeDefined();
expect(result.recommendations.length).toBeGreaterThan(0);
});
it("should handle timeout scenarios", async () => {
// Test timeout handling by creating a scenario that might take time
const largeDir = path.join(testTempDir, "timeout-test");
await fs.mkdir(largeDir, { recursive: true });
// Create multiple markdown files to simulate processing time
for (let i = 0; i < 5; i++) {
await fs.writeFile(
path.join(largeDir, `file${i}.md`),
`
# File ${i}
Content for file ${i} with some text.
\`\`\`javascript
console.log("File ${i}");
\`\`\`
`.trim(),
);
}
const result = await handleValidateDiataxisContent({
contentPath: largeDir,
validationType: "all",
includeCodeValidation: true,
});
expect(result).toBeDefined();
expect(result.confidence).toBeDefined();
});
it("should handle confidence levels and validation modes", async () => {
const testDir = path.join(testTempDir, "confidence-levels");
await fs.mkdir(testDir, { recursive: true });
await fs.writeFile(path.join(testDir, "test.md"), "# Test Content");
// Test different confidence levels
const strictResult = await handleValidateDiataxisContent({
contentPath: testDir,
validationType: "all",
includeCodeValidation: false,
confidence: "strict",
});
expect(strictResult).toBeDefined();
const moderateResult = await handleValidateDiataxisContent({
contentPath: testDir,
validationType: "all",
includeCodeValidation: false,
confidence: "moderate",
});
expect(moderateResult).toBeDefined();
const permissiveResult = await handleValidateDiataxisContent({
contentPath: testDir,
validationType: "all",
includeCodeValidation: false,
confidence: "permissive",
});
expect(permissiveResult).toBeDefined();
});
it("should handle TypeScript files without package.json", async () => {
const tsDir = path.join(testTempDir, "typescript-only");
await fs.mkdir(tsDir, { recursive: true });
await fs.mkdir(path.join(tsDir, "src"), { recursive: true });
// Create TypeScript files without package.json
await fs.writeFile(
path.join(tsDir, "src", "app.ts"),
`
export class TestClass {
public method(): void {
console.log('test');
}
}
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: tsDir,
validationType: "compliance",
includeCodeValidation: false,
});
expect(result).toBeDefined();
expect(result.confidence).toBeDefined();
});
it("should handle mixed content scenarios", async () => {
const mixedDir = path.join(testTempDir, "mixed-content");
await fs.mkdir(mixedDir, { recursive: true });
await fs.mkdir(path.join(mixedDir, "src"), { recursive: true });
// Create both application and documentation content
await fs.writeFile(
path.join(mixedDir, "package.json"),
'{"name": "mixed-app"}',
);
await fs.writeFile(
path.join(mixedDir, "src", "index.ts"),
'export const app = "test";',
);
await fs.writeFile(
path.join(mixedDir, "README.md"),
`
# Mixed Content App
## Installation
Run \`npm install\`
## Usage
See the documentation.
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: mixedDir,
validationType: "all",
includeCodeValidation: true,
});
expect(result).toBeDefined();
expect(
result.confidence.breakdown.architecturalAssumptions,
).toBeGreaterThanOrEqual(60);
});
it("should handle business context alignment scoring", async () => {
const businessDir = path.join(testTempDir, "business-context");
await fs.mkdir(businessDir, { recursive: true });
// Create content with business context
await fs.writeFile(
path.join(businessDir, "business.md"),
`
# Business Requirements
This application serves enterprise customers with specific needs.
The solution addresses market requirements and business objectives.
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: businessDir,
validationType: "all",
includeCodeValidation: false,
});
expect(result).toBeDefined();
expect(
result.confidence.breakdown.businessContextAlignment,
).toBeGreaterThanOrEqual(0);
});
it("should handle deprecated patterns in technical accuracy checks", async () => {
const deprecatedDir = path.join(testTempDir, "deprecated-patterns");
await fs.mkdir(deprecatedDir, { recursive: true });
await fs.writeFile(
path.join(deprecatedDir, "deprecated.md"),
`
# Deprecated Patterns
\`\`\`bash
npm install -g some-package
\`\`\`
\`\`\`javascript
var oldVariable = "test";
function() {
console.log("old style");
}
\`\`\`
Visit http://example.com for more info.
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: deprecatedDir,
validationType: "accuracy",
includeCodeValidation: false,
});
const deprecatedIssues = result.issues.filter((issue) =>
issue.description.includes("Potentially outdated pattern"),
);
expect(deprecatedIssues.length).toBeGreaterThan(0);
});
it("should handle async code without error handling", async () => {
const asyncDir = path.join(testTempDir, "async-code");
await fs.mkdir(asyncDir, { recursive: true });
await fs.writeFile(
path.join(asyncDir, "async.md"),
`
# Async Code Examples
\`\`\`javascript
async function fetchData() {
const response = await fetch('/api/data');
return response.json();
}
\`\`\`
\`\`\`typescript
const getData = async (): Promise<any> => {
const result = await someAsyncOperation();
return result;
};
\`\`\`
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: asyncDir,
validationType: "accuracy",
includeCodeValidation: false,
});
const asyncIssues = result.issues.filter((issue) =>
issue.description.includes("Async code without error handling"),
);
expect(asyncIssues.length).toBeGreaterThan(0);
});
it("should handle version compatibility checks with project context", async () => {
const versionDir = path.join(testTempDir, "version-compat");
await fs.mkdir(versionDir, { recursive: true });
// Create .documcp directory with analysis
const docucmpDir = path.join(versionDir, ".documcp", "analyses");
await fs.mkdir(docucmpDir, { recursive: true });
await fs.writeFile(
path.join(docucmpDir, "version-analysis.json"),
JSON.stringify({
metadata: {
projectName: "version-test",
primaryLanguage: "TypeScript",
},
technologies: { framework: "React" },
dependencies: { packages: ["react@18.2.0", "typescript@4.9.0"] },
}),
);
await fs.writeFile(
path.join(versionDir, "versions.md"),
`
# Version Information
This project uses React @18.2.0 and TypeScript @4.9.0.
Also compatible with Node.js @16.14.0.
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: versionDir,
analysisId: "version-analysis",
validationType: "accuracy",
includeCodeValidation: false,
});
const versionUncertainties = result.uncertainties.filter(
(u) => u.area === "version-compatibility",
);
expect(versionUncertainties.length).toBeGreaterThan(0);
});
it("should handle dangerous bash commands", async () => {
const bashDir = path.join(testTempDir, "dangerous-bash");
await fs.mkdir(bashDir, { recursive: true });
await fs.writeFile(
path.join(bashDir, "dangerous.md"),
`
# Dangerous Commands
\`\`\`bash
rm -rf /
sudo rm -rf /tmp/important
chmod 777 /etc/passwd
command > /dev/null 2>&1
\`\`\`
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: bashDir,
validationType: "accuracy",
includeCodeValidation: false,
});
const dangerousIssues = result.issues.filter((issue) =>
issue.description.includes("Potentially dangerous command"),
);
expect(dangerousIssues.length).toBeGreaterThan(0);
});
it("should handle mixed path separators in commands", async () => {
const pathDir = path.join(testTempDir, "mixed-paths");
await fs.mkdir(pathDir, { recursive: true });
await fs.writeFile(
path.join(pathDir, "paths.md"),
`
# Mixed Path Examples
\`\`\`bash
cp /unix/path\\windows\\mixed /destination/path
\`\`\`
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: pathDir,
validationType: "accuracy",
includeCodeValidation: false,
});
const pathIssues = result.issues.filter((issue) =>
issue.description.includes("Mixed path separators"),
);
expect(pathIssues.length).toBeGreaterThan(0);
});
it("should handle external links in accuracy validation", async () => {
const linksDir = path.join(testTempDir, "external-links");
await fs.mkdir(linksDir, { recursive: true });
await fs.writeFile(
path.join(linksDir, "external.md"),
`
# External Links
[GitHub](https://github.com)
[Documentation](https://docs.example.com)
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: linksDir,
validationType: "accuracy",
includeCodeValidation: false,
});
const linkUncertainties = result.uncertainties.filter(
(u) => u.area === "external-links",
);
expect(linkUncertainties.length).toBeGreaterThan(0);
});
it("should handle Diataxis compliance rules for different sections", async () => {
const complianceDir = path.join(testTempDir, "diataxis-compliance");
await fs.mkdir(complianceDir, { recursive: true });
// Create directories for each Diataxis section
await fs.mkdir(path.join(complianceDir, "tutorials"), {
recursive: true,
});
await fs.mkdir(path.join(complianceDir, "how-to"), { recursive: true });
await fs.mkdir(path.join(complianceDir, "reference"), {
recursive: true,
});
await fs.mkdir(path.join(complianceDir, "explanation"), {
recursive: true,
});
// Tutorial without prerequisites
await fs.writeFile(
path.join(complianceDir, "tutorials", "bad-tutorial.md"),
`
# Bad Tutorial
This tutorial doesn't have prerequisites or clear steps.
`.trim(),
);
// How-to without task focus
await fs.writeFile(
path.join(complianceDir, "how-to", "bad-howto.md"),
`
# Bad Guide
Short guide.
`.trim(),
);
// Reference without structure
await fs.writeFile(
path.join(complianceDir, "reference", "bad-reference.md"),
`
Bad reference without headings or tables.
`.trim(),
);
// Explanation without "why"
await fs.writeFile(
path.join(complianceDir, "explanation", "bad-explanation.md"),
`
# Bad Explanation
Short explanation.
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: complianceDir,
validationType: "compliance",
includeCodeValidation: false,
});
const complianceIssues = result.issues.filter(
(issue) => issue.category === "compliance",
);
expect(complianceIssues.length).toBeGreaterThan(4); // Should find issues in each section
});
it("should handle TypeScript code validation with compilation errors", async () => {
const tsDir = path.join(testTempDir, "typescript-validation");
await fs.mkdir(tsDir, { recursive: true });
await fs.writeFile(
path.join(tsDir, "typescript.md"),
`
# TypeScript Examples
\`\`\`typescript
// This has type errors
let x: string = 123;
function badFunction(param: number): string {
return param; // Type error
}
\`\`\`
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: tsDir,
validationType: "all",
includeCodeValidation: true,
});
expect(result.codeValidation).toBeDefined();
expect(result.codeValidation!.overallSuccess).toBe(false);
});
it("should handle bash code validation with complex chaining", async () => {
const bashComplexDir = path.join(testTempDir, "bash-complex");
await fs.mkdir(bashComplexDir, { recursive: true });
await fs.writeFile(
path.join(bashComplexDir, "complex-bash.md"),
`
# Complex Bash
\`\`\`bash
# Complex command chaining
command1 && command2 || command3
rm $VARIABLE
\`\`\`
`.trim(),
);
const result = await handleValidateDiataxisContent({
contentPath: bashComplexDir,
validationType: "all",
includeCodeValidation: true,
});
expect(result.codeValidation).toBeDefined();
const bashIssues = result.codeValidation!.exampleResults.flatMap(
(ex) => ex.issues,
);
expect(bashIssues.length).toBeGreaterThan(0);
});
it("should handle file limit reached scenario", async () => {
const largeDir = path.join(testTempDir, "large-directory");
await fs.mkdir(largeDir, { recursive: true });
// Create many markdown files to test file limit
for (let i = 0; i < 10; i++) {
await fs.writeFile(
path.join(largeDir, `file${i}.md`),
`# File ${i}\nContent for file ${i}.`,
);
}
const result = await handleValidateDiataxisContent({
contentPath: largeDir,
validationType: "all",
includeCodeValidation: false,
});
expect(result).toBeDefined();
expect(
result.confidence.breakdown.architecturalAssumptions,
).toBeGreaterThan(60);
});
it("should handle symlink detection in file scanning", async () => {
const symlinkDir = path.join(testTempDir, "symlink-test");
await fs.mkdir(symlinkDir, { recursive: true });
// Create a regular file
await fs.writeFile(path.join(symlinkDir, "regular.md"), "# Regular File");
// Create a subdirectory
await fs.mkdir(path.join(symlinkDir, "subdir"), { recursive: true });
await fs.writeFile(
path.join(symlinkDir, "subdir", "nested.md"),
"# Nested File",
);
const result = await handleValidateDiataxisContent({
contentPath: symlinkDir,
validationType: "all",
includeCodeValidation: false,
});
expect(result).toBeDefined();
expect(
result.confidence.breakdown.architecturalAssumptions,
).toBeGreaterThanOrEqual(60);
});
it("should handle timeout scenario", async () => {
const timeoutDir = path.join(testTempDir, "timeout-scenario");
await fs.mkdir(timeoutDir, { recursive: true });
await fs.writeFile(path.join(timeoutDir, "test.md"), "# Test");
// Mock a timeout by creating a very short timeout
const originalTimeout = 120000;
const result = await handleValidateDiataxisContent({
contentPath: timeoutDir,
validationType: "all",
includeCodeValidation: false,
});
expect(result).toBeDefined();
});
it("should handle general content validation with external links", async () => {
const generalDir = path.join(testTempDir, "general-external");
await fs.mkdir(generalDir, { recursive: true });
await fs.writeFile(
path.join(generalDir, "external.md"),
`
# External Links Test
[GitHub](https://github.com)
[Local](./local.md)
`.trim(),
);
await fs.writeFile(path.join(generalDir, "local.md"), "# Local File");
const result = await validateGeneralContent({
contentPath: generalDir,
validationType: "all",
includeCodeValidation: true,
followExternalLinks: false,
});
expect(result.linksChecked).toBeGreaterThan(0);
expect(result.success).toBe(true);
});
it("should handle general content validation with code validation", async () => {
const codeDir = path.join(testTempDir, "general-code");
await fs.mkdir(codeDir, { recursive: true });
await fs.writeFile(
path.join(codeDir, "code.md"),
`
# Code Test
\`\`\`javascript
console.log("test")
\`\`\`
\`\`\`js
console.log("another test");
\`\`\`
`.trim(),
);
const result = await validateGeneralContent({
contentPath: codeDir,
validationType: "code",
includeCodeValidation: true,
});
expect(result.codeBlocksValidated).toBeGreaterThan(0);
expect(result.codeErrors.length).toBeGreaterThan(0); // Missing semicolon
});
it("should handle validation with no code blocks", async () => {
const noCodeDir = path.join(testTempDir, "no-code");
await fs.mkdir(noCodeDir, { recursive: true });
await fs.writeFile(
path.join(noCodeDir, "text.md"),
`
# Text Only
This is just text with no code blocks.
`.trim(),
);
const result = await validateGeneralContent({
contentPath: noCodeDir,
validationType: "all",
includeCodeValidation: true,
});
expect(result.codeBlocksValidated).toBe(0);
expect(result.success).toBe(true);
});
});
});