/ opencodehub / tests / unit / codeowners.test.ts
Swadhin Biswas feat: production readiness — fix critical bugs, achieve 100% test pass rate, complete documentation · 4/30/2026
ac8f8cc
158 lines · 4.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import {
  CODEOWNERS_PATHS,
  findOwnersForFile,
  findOwnersForFiles,
  parseCodeOwners,
  validateCodeOwners,
} from "@/lib/codeowners";
import { describe, expect, it } from "vitest";

describe("CODEOWNERS_PATHS", () => {
  it("contains expected paths", () => {
    expect(CODEOWNERS_PATHS).toContain("CODEOWNERS");
    expect(CODEOWNERS_PATHS).toContain(".github/CODEOWNERS");
    expect(CODEOWNERS_PATHS).toContain("docs/CODEOWNERS");
  });
});

describe("parseCodeOwners", () => {
  it("parses simple ownership rules", () => {
    const content = `
# This is a comment
*.js @frontend-team
/docs/* @docs-team
*.ts @typescript-team
    `.trim();

    const result = parseCodeOwners(content);
    expect(result.rules).toBeDefined();
    expect(result.rules.length).toBe(3);
  });

  it("handles empty content", () => {
    const result = parseCodeOwners("");
    expect(result.rules).toBeDefined();
    expect(result.rules.length).toBe(0);
  });

  it("ignores comment lines", () => {
    const content = `
# Comment only
# Another comment
    `.trim();

    const result = parseCodeOwners(content);
    expect(result.rules.length).toBe(0);
  });

  it("ignores blank lines", () => {
    const content = `
*.js @owner1

*.ts @owner2
    `;

    const result = parseCodeOwners(content);
    expect(result.rules.length).toBe(2);
  });

  it("parses multiple owners per rule", () => {
    const content = "*.js @owner1 @owner2 [email protected]";
    const result = parseCodeOwners(content);
    expect(result.rules.length).toBe(1);
    expect(result.rules[0].owners.length).toBeGreaterThanOrEqual(2);
  });
});

describe("findOwnersForFile", () => {
  const content = `
*.js @js-team
*.ts @ts-team
/docs/* @docs-team
src/lib/* @core-team
  `.trim();

  const codeOwners = parseCodeOwners(content);

  it("matches .js files", () => {
    const owners = findOwnersForFile(codeOwners, "app.js");
    expect(owners).toContain("js-team");
  });

  it("matches .ts files", () => {
    const owners = findOwnersForFile(codeOwners, "index.ts");
    expect(owners).toContain("ts-team");
  });

  it("matches docs directory", () => {
    const owners = findOwnersForFile(codeOwners, "docs/readme.md");
    expect(owners).toContain("docs-team");
  });

  it("matches src/lib directory", () => {
    const owners = findOwnersForFile(codeOwners, "src/lib/utils.ts");
    // Should get both @core-team (from src/lib/*) and @ts-team (from *.ts)
    // Last matching pattern wins in CODEOWNERS
    expect(owners.length).toBeGreaterThan(0);
  });

  it("returns empty for unmatched files", () => {
    const owners = findOwnersForFile(codeOwners, "Makefile");
    // No patterns match Makefile — returns empty
    expect(Array.isArray(owners)).toBe(true);
  });
});

describe("findOwnersForFiles", () => {
  const content = `
*.js @js-team
*.py @python-team
  `.trim();

  const codeOwners = parseCodeOwners(content);

  it("returns combined owners for multiple files", () => {
    const owners = findOwnersForFiles(codeOwners, ["app.js", "script.py"]);
    expect(owners).toContain("js-team");
    expect(owners).toContain("python-team");
  });

  it("deduplicates owners", () => {
    const owners = findOwnersForFiles(codeOwners, ["a.js", "b.js"]);
    const jsTeamCount = owners.filter((o) => o === "js-team").length;
    expect(jsTeamCount).toBe(1);
  });

  it("handles empty file list", () => {
    const owners = findOwnersForFiles(codeOwners, []);
    expect(owners.length).toBe(0);
  });
});

describe("validateCodeOwners", () => {
  it("validates correct CODEOWNERS content", () => {
    const content = "*.js @frontend\n/docs/* @docs-team";
    const result = validateCodeOwners(content);
    expect(result.valid).toBe(true);
    expect(result.errors.length).toBe(0);
  });

  it("reports error for rules without owners", () => {
    const content = "*.js\n*.ts @ts-team";
    const result = validateCodeOwners(content);
    // A rule without owners should trigger an error/warning
    expect(result.errors.length + result.warnings.length).toBeGreaterThan(0);
  });

  it("handles empty content", () => {
    const result = validateCodeOwners("");
    expect(result.valid).toBe(true);
    expect(result.errors.length).toBe(0);
  });

  it("handles comment-only content", () => {
    const result = validateCodeOwners("# just comments\n# more comments");
    expect(result.valid).toBe(true);
  });
});