syntaxai/tdd.md · main · src / c14_go_graph_depth.test.ts

c14_go_graph_depth.test.ts 173 lines · 4078 bytes raw
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);
  });
});