mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
- Add 62 new E2E tests (21 + 22 + 19) - AnalyzeProject.e2e.test.ts: full pipeline testing - CLI.e2e.test.ts: CLI smoke tests with process spawning - JSONOutput.e2e.test.ts: JSON structure validation - 100% test pass rate achieved (519/519 tests) - Update ROADMAP.md and CHANGELOG.md - Bump version to 0.7.8
413 lines
18 KiB
TypeScript
413 lines
18 KiB
TypeScript
import { describe, it, expect } from "vitest"
|
|
import { analyzeProject } from "../../src/api"
|
|
import path from "path"
|
|
import type {
|
|
AnalyzeProjectResponse,
|
|
HardcodeViolation,
|
|
CircularDependencyViolation,
|
|
NamingConventionViolation,
|
|
FrameworkLeakViolation,
|
|
EntityExposureViolation,
|
|
DependencyDirectionViolation,
|
|
RepositoryPatternViolation,
|
|
AggregateBoundaryViolation,
|
|
} from "../../src/api"
|
|
|
|
describe("JSON Output Format E2E", () => {
|
|
const EXAMPLES_DIR = path.join(__dirname, "../../examples")
|
|
|
|
describe("Response Structure", () => {
|
|
it("should return valid JSON structure", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "good-architecture")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
|
|
expect(result).toBeDefined()
|
|
expect(typeof result).toBe("object")
|
|
|
|
const json = JSON.stringify(result)
|
|
expect(() => JSON.parse(json)).not.toThrow()
|
|
})
|
|
|
|
it("should include all required top-level fields", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "good-architecture")
|
|
|
|
const result: AnalyzeProjectResponse = await analyzeProject({ rootDir })
|
|
|
|
expect(result).toHaveProperty("hardcodeViolations")
|
|
expect(result).toHaveProperty("violations")
|
|
expect(result).toHaveProperty("circularDependencyViolations")
|
|
expect(result).toHaveProperty("namingViolations")
|
|
expect(result).toHaveProperty("frameworkLeakViolations")
|
|
expect(result).toHaveProperty("entityExposureViolations")
|
|
expect(result).toHaveProperty("dependencyDirectionViolations")
|
|
expect(result).toHaveProperty("repositoryPatternViolations")
|
|
expect(result).toHaveProperty("aggregateBoundaryViolations")
|
|
expect(result).toHaveProperty("metrics")
|
|
expect(result).toHaveProperty("dependencyGraph")
|
|
})
|
|
|
|
it("should have correct types for all fields", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "good-architecture")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
|
|
expect(Array.isArray(result.hardcodeViolations)).toBe(true)
|
|
expect(Array.isArray(result.violations)).toBe(true)
|
|
expect(Array.isArray(result.circularDependencyViolations)).toBe(true)
|
|
expect(Array.isArray(result.namingViolations)).toBe(true)
|
|
expect(Array.isArray(result.frameworkLeakViolations)).toBe(true)
|
|
expect(Array.isArray(result.entityExposureViolations)).toBe(true)
|
|
expect(Array.isArray(result.dependencyDirectionViolations)).toBe(true)
|
|
expect(Array.isArray(result.repositoryPatternViolations)).toBe(true)
|
|
expect(Array.isArray(result.aggregateBoundaryViolations)).toBe(true)
|
|
expect(typeof result.metrics).toBe("object")
|
|
expect(typeof result.dependencyGraph).toBe("object")
|
|
})
|
|
})
|
|
|
|
describe("Metrics Structure", () => {
|
|
it("should include all metric fields", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "good-architecture")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
const { metrics } = result
|
|
|
|
expect(metrics).toHaveProperty("totalFiles")
|
|
expect(metrics).toHaveProperty("totalFunctions")
|
|
expect(metrics).toHaveProperty("totalImports")
|
|
expect(metrics).toHaveProperty("layerDistribution")
|
|
|
|
expect(typeof metrics.totalFiles).toBe("number")
|
|
expect(typeof metrics.totalFunctions).toBe("number")
|
|
expect(typeof metrics.totalImports).toBe("number")
|
|
expect(typeof metrics.layerDistribution).toBe("object")
|
|
})
|
|
|
|
it("should have non-negative metric values", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "good-architecture")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
const { metrics } = result
|
|
|
|
expect(metrics.totalFiles).toBeGreaterThanOrEqual(0)
|
|
expect(metrics.totalFunctions).toBeGreaterThanOrEqual(0)
|
|
expect(metrics.totalImports).toBeGreaterThanOrEqual(0)
|
|
})
|
|
})
|
|
|
|
describe("Hardcode Violation Structure", () => {
|
|
it("should have correct structure for hardcode violations", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "bad-architecture/hardcoded")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
|
|
if (result.hardcodeViolations.length > 0) {
|
|
const violation: HardcodeViolation = result.hardcodeViolations[0]
|
|
|
|
expect(violation).toHaveProperty("file")
|
|
expect(violation).toHaveProperty("line")
|
|
expect(violation).toHaveProperty("column")
|
|
expect(violation).toHaveProperty("type")
|
|
expect(violation).toHaveProperty("value")
|
|
expect(violation).toHaveProperty("context")
|
|
expect(violation).toHaveProperty("suggestion")
|
|
expect(violation).toHaveProperty("severity")
|
|
|
|
expect(typeof violation.file).toBe("string")
|
|
expect(typeof violation.line).toBe("number")
|
|
expect(typeof violation.column).toBe("number")
|
|
expect(typeof violation.type).toBe("string")
|
|
expect(typeof violation.context).toBe("string")
|
|
expect(typeof violation.severity).toBe("string")
|
|
}
|
|
})
|
|
})
|
|
|
|
describe("Circular Dependency Violation Structure", () => {
|
|
it("should have correct structure for circular dependency violations", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "bad-architecture/circular")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
|
|
if (result.circularDependencyViolations.length > 0) {
|
|
const violation: CircularDependencyViolation =
|
|
result.circularDependencyViolations[0]
|
|
|
|
expect(violation).toHaveProperty("file")
|
|
expect(violation).toHaveProperty("cycle")
|
|
expect(violation).toHaveProperty("severity")
|
|
|
|
expect(typeof violation.file).toBe("string")
|
|
expect(Array.isArray(violation.cycle)).toBe(true)
|
|
expect(violation.cycle.length).toBeGreaterThanOrEqual(2)
|
|
expect(typeof violation.severity).toBe("string")
|
|
expect(violation.severity).toBe("critical")
|
|
}
|
|
})
|
|
})
|
|
|
|
describe("Naming Convention Violation Structure", () => {
|
|
it("should have correct structure for naming violations", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "bad-architecture/naming")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
|
|
if (result.namingViolations.length > 0) {
|
|
const violation: NamingConventionViolation = result.namingViolations[0]
|
|
|
|
expect(violation).toHaveProperty("file")
|
|
expect(violation).toHaveProperty("fileName")
|
|
expect(violation).toHaveProperty("expected")
|
|
expect(violation).toHaveProperty("actual")
|
|
expect(violation).toHaveProperty("layer")
|
|
expect(violation).toHaveProperty("message")
|
|
expect(violation).toHaveProperty("severity")
|
|
|
|
expect(typeof violation.file).toBe("string")
|
|
expect(typeof violation.fileName).toBe("string")
|
|
expect(typeof violation.expected).toBe("string")
|
|
expect(typeof violation.actual).toBe("string")
|
|
expect(typeof violation.layer).toBe("string")
|
|
expect(typeof violation.message).toBe("string")
|
|
expect(typeof violation.severity).toBe("string")
|
|
}
|
|
})
|
|
})
|
|
|
|
describe("Framework Leak Violation Structure", () => {
|
|
it("should have correct structure for framework leak violations", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "bad-architecture/framework-leaks")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
|
|
if (result.frameworkLeakViolations.length > 0) {
|
|
const violation: FrameworkLeakViolation = result.frameworkLeakViolations[0]
|
|
|
|
expect(violation).toHaveProperty("file")
|
|
expect(violation).toHaveProperty("packageName")
|
|
expect(violation).toHaveProperty("category")
|
|
expect(violation).toHaveProperty("categoryDescription")
|
|
expect(violation).toHaveProperty("layer")
|
|
expect(violation).toHaveProperty("message")
|
|
expect(violation).toHaveProperty("suggestion")
|
|
expect(violation).toHaveProperty("severity")
|
|
|
|
expect(typeof violation.file).toBe("string")
|
|
expect(typeof violation.packageName).toBe("string")
|
|
expect(typeof violation.category).toBe("string")
|
|
expect(typeof violation.categoryDescription).toBe("string")
|
|
expect(typeof violation.layer).toBe("string")
|
|
expect(typeof violation.message).toBe("string")
|
|
expect(typeof violation.suggestion).toBe("string")
|
|
expect(typeof violation.severity).toBe("string")
|
|
}
|
|
})
|
|
})
|
|
|
|
describe("Entity Exposure Violation Structure", () => {
|
|
it("should have correct structure for entity exposure violations", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "bad-architecture/entity-exposure")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
|
|
if (result.entityExposureViolations.length > 0) {
|
|
const violation: EntityExposureViolation = result.entityExposureViolations[0]
|
|
|
|
expect(violation).toHaveProperty("file")
|
|
expect(violation).toHaveProperty("entityName")
|
|
expect(violation).toHaveProperty("returnType")
|
|
expect(violation).toHaveProperty("suggestion")
|
|
expect(violation).toHaveProperty("severity")
|
|
|
|
expect(typeof violation.file).toBe("string")
|
|
expect(typeof violation.entityName).toBe("string")
|
|
expect(typeof violation.returnType).toBe("string")
|
|
expect(typeof violation.suggestion).toBe("string")
|
|
expect(typeof violation.severity).toBe("string")
|
|
}
|
|
})
|
|
})
|
|
|
|
describe("Dependency Direction Violation Structure", () => {
|
|
it("should have correct structure for dependency direction violations", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "bad-architecture/dependency-direction")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
|
|
if (result.dependencyDirectionViolations.length > 0) {
|
|
const violation: DependencyDirectionViolation =
|
|
result.dependencyDirectionViolations[0]
|
|
|
|
expect(violation).toHaveProperty("file")
|
|
expect(violation).toHaveProperty("fromLayer")
|
|
expect(violation).toHaveProperty("toLayer")
|
|
expect(violation).toHaveProperty("importPath")
|
|
expect(violation).toHaveProperty("suggestion")
|
|
expect(violation).toHaveProperty("severity")
|
|
|
|
expect(typeof violation.file).toBe("string")
|
|
expect(typeof violation.fromLayer).toBe("string")
|
|
expect(typeof violation.toLayer).toBe("string")
|
|
expect(typeof violation.importPath).toBe("string")
|
|
expect(typeof violation.suggestion).toBe("string")
|
|
expect(typeof violation.severity).toBe("string")
|
|
}
|
|
})
|
|
})
|
|
|
|
describe("Repository Pattern Violation Structure", () => {
|
|
it("should have correct structure for repository pattern violations", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "repository-pattern")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
|
|
const badViolations = result.repositoryPatternViolations.filter((v) =>
|
|
v.file.includes("bad"),
|
|
)
|
|
|
|
if (badViolations.length > 0) {
|
|
const violation: RepositoryPatternViolation = badViolations[0]
|
|
|
|
expect(violation).toHaveProperty("file")
|
|
expect(violation).toHaveProperty("line")
|
|
expect(violation).toHaveProperty("violationType")
|
|
expect(violation).toHaveProperty("details")
|
|
expect(violation).toHaveProperty("suggestion")
|
|
expect(violation).toHaveProperty("severity")
|
|
|
|
expect(typeof violation.file).toBe("string")
|
|
expect(typeof violation.line).toBe("number")
|
|
expect(typeof violation.violationType).toBe("string")
|
|
expect(typeof violation.details).toBe("string")
|
|
expect(typeof violation.suggestion).toBe("string")
|
|
expect(typeof violation.severity).toBe("string")
|
|
}
|
|
})
|
|
})
|
|
|
|
describe("Aggregate Boundary Violation Structure", () => {
|
|
it("should have correct structure for aggregate boundary violations", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "aggregate-boundary/bad")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
|
|
if (result.aggregateBoundaryViolations.length > 0) {
|
|
const violation: AggregateBoundaryViolation = result.aggregateBoundaryViolations[0]
|
|
|
|
expect(violation).toHaveProperty("file")
|
|
expect(violation).toHaveProperty("fromAggregate")
|
|
expect(violation).toHaveProperty("toAggregate")
|
|
expect(violation).toHaveProperty("entityName")
|
|
expect(violation).toHaveProperty("importPath")
|
|
expect(violation).toHaveProperty("suggestion")
|
|
expect(violation).toHaveProperty("severity")
|
|
|
|
expect(typeof violation.file).toBe("string")
|
|
expect(typeof violation.fromAggregate).toBe("string")
|
|
expect(typeof violation.toAggregate).toBe("string")
|
|
expect(typeof violation.entityName).toBe("string")
|
|
expect(typeof violation.importPath).toBe("string")
|
|
expect(typeof violation.suggestion).toBe("string")
|
|
expect(typeof violation.severity).toBe("string")
|
|
}
|
|
})
|
|
})
|
|
|
|
describe("Dependency Graph Structure", () => {
|
|
it("should have dependency graph object", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "good-architecture")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
const { dependencyGraph } = result
|
|
|
|
expect(dependencyGraph).toBeDefined()
|
|
expect(typeof dependencyGraph).toBe("object")
|
|
})
|
|
|
|
it("should have getAllNodes method on dependency graph", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "good-architecture")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
const { dependencyGraph } = result
|
|
|
|
expect(typeof dependencyGraph.getAllNodes).toBe("function")
|
|
const nodes = dependencyGraph.getAllNodes()
|
|
expect(Array.isArray(nodes)).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe("JSON Serialization", () => {
|
|
it("should serialize metrics without data loss", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "good-architecture")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
|
|
const json = JSON.stringify(result.metrics)
|
|
const parsed = JSON.parse(json)
|
|
|
|
expect(parsed.totalFiles).toBe(result.metrics.totalFiles)
|
|
expect(parsed.totalFunctions).toBe(result.metrics.totalFunctions)
|
|
expect(parsed.totalImports).toBe(result.metrics.totalImports)
|
|
})
|
|
|
|
it("should serialize violations without data loss", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "good-architecture")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
|
|
const json = JSON.stringify({
|
|
hardcodeViolations: result.hardcodeViolations,
|
|
violations: result.violations,
|
|
})
|
|
const parsed = JSON.parse(json)
|
|
|
|
expect(Array.isArray(parsed.violations)).toBe(true)
|
|
expect(Array.isArray(parsed.hardcodeViolations)).toBe(true)
|
|
})
|
|
|
|
it("should serialize violation arrays for large results", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "bad-architecture")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
|
|
const json = JSON.stringify({
|
|
hardcodeViolations: result.hardcodeViolations,
|
|
violations: result.violations,
|
|
namingViolations: result.namingViolations,
|
|
})
|
|
|
|
expect(json.length).toBeGreaterThan(0)
|
|
expect(() => JSON.parse(json)).not.toThrow()
|
|
})
|
|
})
|
|
|
|
describe("Severity Levels", () => {
|
|
it("should only contain valid severity levels", async () => {
|
|
const rootDir = path.join(EXAMPLES_DIR, "bad-architecture")
|
|
|
|
const result = await analyzeProject({ rootDir })
|
|
|
|
const validSeverities = ["critical", "high", "medium", "low"]
|
|
|
|
const allViolations = [
|
|
...result.hardcodeViolations,
|
|
...result.violations,
|
|
...result.circularDependencyViolations,
|
|
...result.namingViolations,
|
|
...result.frameworkLeakViolations,
|
|
...result.entityExposureViolations,
|
|
...result.dependencyDirectionViolations,
|
|
...result.repositoryPatternViolations,
|
|
...result.aggregateBoundaryViolations,
|
|
]
|
|
|
|
allViolations.forEach((violation) => {
|
|
if ("severity" in violation) {
|
|
expect(validSeverities).toContain(violation.severity)
|
|
}
|
|
})
|
|
})
|
|
})
|
|
})
|