Files
puaros/packages/guardian/tests/unit/domain/DependencyGraph.test.ts
imfozilbek 03705b5264 feat(guardian): add guardian package - code quality analyzer
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.
2025-11-24 02:54:39 +05:00

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)
})
})
})