import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { resolve } from "node:path"; import { collectGoImports, computeGoGraphDepth, parseGoModulePath, } from "./c14_go_graph_depth.ts"; const FIXTURE = mkdtempSync(resolve(tmpdir(), "tdd-md-go-graph-")); const writeFile = (rel: string, content: string): void => { const abs = resolve(FIXTURE, rel); mkdirSync(abs.split("/").slice(0, -1).join("/"), { recursive: true }); writeFileSync(abs, content); }; beforeAll(() => { writeFile( "go.mod", `module github.com/example/fixture go 1.22 `, ); // Three packages forming a chain entry → middle → leaf, plus // some external imports we should NOT count. writeFile( "cmd/entry/main.go", `package main import ( "fmt" "github.com/example/fixture/internal/middle" "github.com/example/external/library" ) func main() { fmt.Println(middle.X) _ = library.Y } `, ); writeFile( "internal/middle/middle.go", `package middle import ( "github.com/example/fixture/internal/leaf" ) var X = leaf.Z `, ); writeFile( "internal/leaf/leaf.go", `package leaf var Z = 1 `, ); // A test file that should be excluded. writeFile( "internal/leaf/leaf_test.go", `package leaf import ( "testing" "github.com/example/fixture/internal/middle" ) func TestZ(t *testing.T) { _ = middle.X } `, ); // A vendored file that should also be skipped. writeFile( "vendor/some/lib.go", `package some import "github.com/example/fixture/cmd/entry" var _ = entry.X `, ); }); afterAll(() => { rmSync(FIXTURE, { recursive: true, force: true }); }); describe("parseGoModulePath", () => { test("extracts the module path from a typical go.mod", () => { expect(parseGoModulePath('module github.com/x/y\n\ngo 1.22\n')) .toBe('github.com/x/y'); }); test("handles quoted module paths", () => { expect(parseGoModulePath('module "github.com/x/y"\n')) .toBe('github.com/x/y'); }); test("throws when the go.mod has no module directive", () => { expect(() => parseGoModulePath('go 1.22\n')).toThrow(/module/); }); }); describe("collectGoImports", () => { test("single-line import", () => { expect(collectGoImports('package x\n\nimport "fmt"\n')).toEqual(['fmt']); }); test("block import", () => { const imports = collectGoImports(`package x import ( "fmt" "strings" "github.com/x/y" ) `); expect(imports).toEqual(['fmt', 'strings', 'github.com/x/y']); }); test("aliased imports", () => { const imports = collectGoImports(`package x import ( myfmt "fmt" _ "side-effect/pkg" ) `); expect(imports).toEqual(['fmt', 'side-effect/pkg']); }); test("ignores commented-out imports", () => { const imports = collectGoImports(`package x // import "ignored" import "fmt" `); expect(imports).toEqual(['fmt']); }); }); describe("computeGoGraphDepth — end-to-end on fixture", () => { test("entry → middle → leaf chain produces depth 3", () => { const r = computeGoGraphDepth(FIXTURE); expect(r.language).toBe('go'); expect(r.modulePath).toBe('github.com/example/fixture'); // Three intra-module package directories: cmd/entry, // internal/middle, internal/leaf. (vendor/some excluded.) expect(r.nodeCount).toBe(3); // Two intra-module edges: cmd/entry → internal/middle, // internal/middle → internal/leaf. (External and vendored // edges excluded; the _test.go edge to middle excluded because // _test.go files are skipped.) expect(r.edgeCount).toBe(2); expect(r.depth).toBe(3); }); test("result echoes the modulePath so callers can audit", () => { const r = computeGoGraphDepth(FIXTURE); expect(r.modulePath).toBe('github.com/example/fixture'); }); test("re-running on the same tree produces identical numbers", () => { const a = computeGoGraphDepth(FIXTURE); const b = computeGoGraphDepth(FIXTURE); expect(a).toEqual(b); }); });