mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
Add @puaros/guardian package v0.1.0 - code quality guardian for vibe coders and enterprise teams. Features: - Hardcode detection (magic numbers, magic strings) - Circular dependency detection - Naming convention enforcement (Clean Architecture) - Architecture violation detection - CLI tool with comprehensive reporting - 159 tests with 80%+ coverage - Smart suggestions for fixes - Built for AI-assisted development Built with Clean Architecture and DDD principles. Works with Claude, GPT, Copilot, Cursor, and any AI coding assistant.
235 lines
9.2 KiB
TypeScript
235 lines
9.2 KiB
TypeScript
import { describe, it, expect } from "vitest"
|
|
import { DependencyGraph } from "../../../src/domain/entities/DependencyGraph"
|
|
import { SourceFile } from "../../../src/domain/entities/SourceFile"
|
|
import { ProjectPath } from "../../../src/domain/value-objects/ProjectPath"
|
|
|
|
describe("DependencyGraph", () => {
|
|
describe("basic operations", () => {
|
|
it("should create an empty dependency graph", () => {
|
|
const graph = new DependencyGraph()
|
|
expect(graph.getAllNodes()).toHaveLength(0)
|
|
})
|
|
|
|
it("should add a file to the graph", () => {
|
|
const graph = new DependencyGraph()
|
|
const path = ProjectPath.create("/project/src/file.ts", "/project")
|
|
const file = new SourceFile(path, "const x = 1")
|
|
|
|
graph.addFile(file)
|
|
|
|
expect(graph.getAllNodes()).toHaveLength(1)
|
|
expect(graph.getNode("src/file.ts")).toBeDefined()
|
|
})
|
|
|
|
it("should add dependencies between files", () => {
|
|
const graph = new DependencyGraph()
|
|
const path1 = ProjectPath.create("/project/src/file1.ts", "/project")
|
|
const path2 = ProjectPath.create("/project/src/file2.ts", "/project")
|
|
const file1 = new SourceFile(path1, "import { x } from './file2'")
|
|
const file2 = new SourceFile(path2, "export const x = 1")
|
|
|
|
graph.addFile(file1)
|
|
graph.addFile(file2)
|
|
graph.addDependency("src/file1.ts", "src/file2.ts")
|
|
|
|
const node1 = graph.getNode("src/file1.ts")
|
|
expect(node1?.dependencies).toContain("src/file2.ts")
|
|
|
|
const node2 = graph.getNode("src/file2.ts")
|
|
expect(node2?.dependents).toContain("src/file1.ts")
|
|
})
|
|
|
|
it("should get metrics", () => {
|
|
const graph = new DependencyGraph()
|
|
const path1 = ProjectPath.create("/project/src/file1.ts", "/project")
|
|
const path2 = ProjectPath.create("/project/src/file2.ts", "/project")
|
|
const file1 = new SourceFile(path1, "")
|
|
const file2 = new SourceFile(path2, "")
|
|
|
|
graph.addFile(file1)
|
|
graph.addFile(file2)
|
|
graph.addDependency("src/file1.ts", "src/file2.ts")
|
|
|
|
const metrics = graph.getMetrics()
|
|
expect(metrics.totalFiles).toBe(2)
|
|
expect(metrics.totalDependencies).toBe(1)
|
|
})
|
|
})
|
|
|
|
describe("findCycles", () => {
|
|
it("should return empty array when no cycles exist", () => {
|
|
const graph = new DependencyGraph()
|
|
const path1 = ProjectPath.create("/project/src/a.ts", "/project")
|
|
const path2 = ProjectPath.create("/project/src/b.ts", "/project")
|
|
const path3 = ProjectPath.create("/project/src/c.ts", "/project")
|
|
|
|
const fileA = new SourceFile(path1, "")
|
|
const fileB = new SourceFile(path2, "")
|
|
const fileC = new SourceFile(path3, "")
|
|
|
|
graph.addFile(fileA)
|
|
graph.addFile(fileB)
|
|
graph.addFile(fileC)
|
|
|
|
graph.addDependency("src/a.ts", "src/b.ts")
|
|
graph.addDependency("src/b.ts", "src/c.ts")
|
|
|
|
const cycles = graph.findCycles()
|
|
expect(cycles).toHaveLength(0)
|
|
})
|
|
|
|
it("should detect simple two-file cycle (A → B → A)", () => {
|
|
const graph = new DependencyGraph()
|
|
const pathA = ProjectPath.create("/project/src/a.ts", "/project")
|
|
const pathB = ProjectPath.create("/project/src/b.ts", "/project")
|
|
|
|
const fileA = new SourceFile(pathA, "import { b } from './b'")
|
|
const fileB = new SourceFile(pathB, "import { a } from './a'")
|
|
|
|
graph.addFile(fileA)
|
|
graph.addFile(fileB)
|
|
graph.addDependency("src/a.ts", "src/b.ts")
|
|
graph.addDependency("src/b.ts", "src/a.ts")
|
|
|
|
const cycles = graph.findCycles()
|
|
expect(cycles.length).toBeGreaterThan(0)
|
|
|
|
const cycle = cycles[0]
|
|
expect(cycle).toContain("src/a.ts")
|
|
expect(cycle).toContain("src/b.ts")
|
|
})
|
|
|
|
it("should detect three-file cycle (A → B → C → A)", () => {
|
|
const graph = new DependencyGraph()
|
|
const pathA = ProjectPath.create("/project/src/a.ts", "/project")
|
|
const pathB = ProjectPath.create("/project/src/b.ts", "/project")
|
|
const pathC = ProjectPath.create("/project/src/c.ts", "/project")
|
|
|
|
const fileA = new SourceFile(pathA, "")
|
|
const fileB = new SourceFile(pathB, "")
|
|
const fileC = new SourceFile(pathC, "")
|
|
|
|
graph.addFile(fileA)
|
|
graph.addFile(fileB)
|
|
graph.addFile(fileC)
|
|
|
|
graph.addDependency("src/a.ts", "src/b.ts")
|
|
graph.addDependency("src/b.ts", "src/c.ts")
|
|
graph.addDependency("src/c.ts", "src/a.ts")
|
|
|
|
const cycles = graph.findCycles()
|
|
expect(cycles.length).toBeGreaterThan(0)
|
|
|
|
const cycle = cycles[0]
|
|
expect(cycle).toContain("src/a.ts")
|
|
expect(cycle).toContain("src/b.ts")
|
|
expect(cycle).toContain("src/c.ts")
|
|
})
|
|
|
|
it("should detect longer cycles (A → B → C → D → A)", () => {
|
|
const graph = new DependencyGraph()
|
|
const pathA = ProjectPath.create("/project/src/a.ts", "/project")
|
|
const pathB = ProjectPath.create("/project/src/b.ts", "/project")
|
|
const pathC = ProjectPath.create("/project/src/c.ts", "/project")
|
|
const pathD = ProjectPath.create("/project/src/d.ts", "/project")
|
|
|
|
const fileA = new SourceFile(pathA, "")
|
|
const fileB = new SourceFile(pathB, "")
|
|
const fileC = new SourceFile(pathC, "")
|
|
const fileD = new SourceFile(pathD, "")
|
|
|
|
graph.addFile(fileA)
|
|
graph.addFile(fileB)
|
|
graph.addFile(fileC)
|
|
graph.addFile(fileD)
|
|
|
|
graph.addDependency("src/a.ts", "src/b.ts")
|
|
graph.addDependency("src/b.ts", "src/c.ts")
|
|
graph.addDependency("src/c.ts", "src/d.ts")
|
|
graph.addDependency("src/d.ts", "src/a.ts")
|
|
|
|
const cycles = graph.findCycles()
|
|
expect(cycles.length).toBeGreaterThan(0)
|
|
|
|
const cycle = cycles[0]
|
|
expect(cycle.length).toBe(4)
|
|
expect(cycle).toContain("src/a.ts")
|
|
expect(cycle).toContain("src/b.ts")
|
|
expect(cycle).toContain("src/c.ts")
|
|
expect(cycle).toContain("src/d.ts")
|
|
})
|
|
|
|
it("should detect multiple independent cycles", () => {
|
|
const graph = new DependencyGraph()
|
|
|
|
const pathA = ProjectPath.create("/project/src/a.ts", "/project")
|
|
const pathB = ProjectPath.create("/project/src/b.ts", "/project")
|
|
const pathC = ProjectPath.create("/project/src/c.ts", "/project")
|
|
const pathD = ProjectPath.create("/project/src/d.ts", "/project")
|
|
|
|
const fileA = new SourceFile(pathA, "")
|
|
const fileB = new SourceFile(pathB, "")
|
|
const fileC = new SourceFile(pathC, "")
|
|
const fileD = new SourceFile(pathD, "")
|
|
|
|
graph.addFile(fileA)
|
|
graph.addFile(fileB)
|
|
graph.addFile(fileC)
|
|
graph.addFile(fileD)
|
|
|
|
graph.addDependency("src/a.ts", "src/b.ts")
|
|
graph.addDependency("src/b.ts", "src/a.ts")
|
|
|
|
graph.addDependency("src/c.ts", "src/d.ts")
|
|
graph.addDependency("src/d.ts", "src/c.ts")
|
|
|
|
const cycles = graph.findCycles()
|
|
expect(cycles.length).toBeGreaterThanOrEqual(2)
|
|
})
|
|
|
|
it("should handle complex graph with cycle and acyclic parts", () => {
|
|
const graph = new DependencyGraph()
|
|
|
|
const pathA = ProjectPath.create("/project/src/a.ts", "/project")
|
|
const pathB = ProjectPath.create("/project/src/b.ts", "/project")
|
|
const pathC = ProjectPath.create("/project/src/c.ts", "/project")
|
|
const pathD = ProjectPath.create("/project/src/d.ts", "/project")
|
|
|
|
const fileA = new SourceFile(pathA, "")
|
|
const fileB = new SourceFile(pathB, "")
|
|
const fileC = new SourceFile(pathC, "")
|
|
const fileD = new SourceFile(pathD, "")
|
|
|
|
graph.addFile(fileA)
|
|
graph.addFile(fileB)
|
|
graph.addFile(fileC)
|
|
graph.addFile(fileD)
|
|
|
|
graph.addDependency("src/a.ts", "src/b.ts")
|
|
graph.addDependency("src/b.ts", "src/a.ts")
|
|
|
|
graph.addDependency("src/c.ts", "src/d.ts")
|
|
|
|
const cycles = graph.findCycles()
|
|
expect(cycles.length).toBeGreaterThan(0)
|
|
|
|
const cycle = cycles[0]
|
|
expect(cycle).toContain("src/a.ts")
|
|
expect(cycle).toContain("src/b.ts")
|
|
expect(cycle).not.toContain("src/c.ts")
|
|
expect(cycle).not.toContain("src/d.ts")
|
|
})
|
|
|
|
it("should handle single file without dependencies", () => {
|
|
const graph = new DependencyGraph()
|
|
const path = ProjectPath.create("/project/src/a.ts", "/project")
|
|
const file = new SourceFile(path, "")
|
|
|
|
graph.addFile(file)
|
|
|
|
const cycles = graph.findCycles()
|
|
expect(cycles).toHaveLength(0)
|
|
})
|
|
})
|
|
})
|