Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

deps.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

deps.test.tsBlame261 lines · 1 contributor
8098672Claude1/**
2 * Block J1 — Dependency graph tests.
3 *
4 * Parser smokes for each supported ecosystem + auth smokes for routes.
5 */
6
7import { describe, it, expect } from "bun:test";
8import app from "../app";
9import {
10 parsePackageJson,
11 parseRequirementsTxt,
12 parsePyprojectToml,
13 parseGoMod,
14 parseCargoToml,
15 parseGemfile,
16 parseComposerJson,
17 parseManifest,
18 isManifestPath,
19 __internal,
20} from "../lib/deps";
21
22describe("deps — isManifestPath", () => {
23 it("recognises supported manifests", () => {
24 expect(isManifestPath("package.json")).toBe(true);
25 expect(isManifestPath("frontend/package.json")).toBe(true);
26 expect(isManifestPath("requirements.txt")).toBe(true);
27 expect(isManifestPath("pyproject.toml")).toBe(true);
28 expect(isManifestPath("go.mod")).toBe(true);
29 expect(isManifestPath("Cargo.toml")).toBe(true);
30 expect(isManifestPath("Gemfile")).toBe(true);
31 expect(isManifestPath("composer.json")).toBe(true);
32 });
33
34 it("rejects unrelated files", () => {
35 expect(isManifestPath("README.md")).toBe(false);
36 expect(isManifestPath("src/index.ts")).toBe(false);
37 expect(isManifestPath("package-lock.json")).toBe(false);
38 });
39});
40
41describe("deps — parsePackageJson", () => {
42 it("extracts dependencies and devDependencies", () => {
43 const deps = parsePackageJson(
44 JSON.stringify({
45 dependencies: { hono: "^4.0.0", "drizzle-orm": "0.30.0" },
46 devDependencies: { typescript: "^5.0.0" },
47 peerDependencies: { react: ">=18" },
48 })
49 );
50 expect(deps).toHaveLength(4);
51 const hono = deps.find((d) => d.name === "hono")!;
52 expect(hono.ecosystem).toBe("npm");
53 expect(hono.versionSpec).toBe("^4.0.0");
54 expect(hono.isDev).toBe(false);
55 const ts = deps.find((d) => d.name === "typescript")!;
56 expect(ts.isDev).toBe(true);
57 });
58
59 it("returns empty on bad JSON", () => {
60 expect(parsePackageJson("not json")).toEqual([]);
61 });
62
63 it("returns empty when no deps", () => {
64 expect(parsePackageJson(JSON.stringify({ name: "x", version: "1" }))).toEqual(
65 []
66 );
67 });
68});
69
70describe("deps — parseRequirementsTxt", () => {
71 it("parses canonical form", () => {
72 const deps = parseRequirementsTxt(
73 `# comment
74requests==2.28.0
75Flask>=2.0,<3.0
76numpy
77pytest # inline comment`
78 );
79 const names = deps.map((d) => d.name);
80 expect(names).toContain("requests");
81 expect(names).toContain("Flask");
82 expect(names).toContain("numpy");
83 expect(names).toContain("pytest");
84 expect(deps.find((d) => d.name === "requests")!.versionSpec).toBe(
85 "==2.28.0"
86 );
87 });
88
89 it("skips blank lines and editable installs", () => {
90 const deps = parseRequirementsTxt(
91 `
92# just a comment
93-e git+https://example.com/foo.git#egg=foo
94--index-url https://pypi.org/simple/
95`
96 );
97 expect(deps).toHaveLength(0);
98 });
99
100 it("handles extras syntax", () => {
101 const deps = parseRequirementsTxt("requests[security]==2.0");
102 expect(deps[0].name).toBe("requests");
103 });
104});
105
106describe("deps — parsePyprojectToml", () => {
107 it("extracts project.dependencies", () => {
108 const deps = parsePyprojectToml(`
109[project]
110name = "myapp"
111dependencies = [
112 "requests>=2.0",
113 "flask"
114]
115`);
116 const names = deps.map((d) => d.name).sort();
117 expect(names).toContain("requests");
118 expect(names).toContain("flask");
119 });
120
121 it("extracts optional-dependencies as dev", () => {
122 const deps = parsePyprojectToml(`
123[project.optional-dependencies]
124dev = ["pytest", "black"]
125`);
126 expect(deps.some((d) => d.name === "pytest" && d.isDev)).toBe(true);
127 });
128});
129
130describe("deps — parseGoMod", () => {
131 it("parses require block", () => {
132 const deps = parseGoMod(`
133module example.com/foo
134
135go 1.21
136
137require (
138 github.com/gorilla/mux v1.8.0
139 github.com/jackc/pgx/v5 v5.4.3 // indirect
140)
141`);
142 expect(deps.length).toBeGreaterThanOrEqual(2);
143 expect(deps.find((d) => d.name === "github.com/gorilla/mux")!.versionSpec)
144 .toBe("v1.8.0");
145 });
146});
147
148describe("deps — parseCargoToml", () => {
149 it("parses [dependencies] and [dev-dependencies]", () => {
150 const deps = parseCargoToml(`
151[package]
152name = "myapp"
153
154[dependencies]
155serde = "1.0"
156tokio = { version = "1.35", features = ["full"] }
157
158[dev-dependencies]
159criterion = "0.5"
160`);
161 const serde = deps.find((d) => d.name === "serde")!;
162 expect(serde.versionSpec).toBe("1.0");
163 expect(serde.isDev).toBe(false);
164 const tokio = deps.find((d) => d.name === "tokio")!;
165 expect(tokio.versionSpec).toBe("1.35");
166 const criterion = deps.find((d) => d.name === "criterion")!;
167 expect(criterion.isDev).toBe(true);
168 });
169});
170
171describe("deps — parseGemfile", () => {
172 it("parses gem lines", () => {
173 const deps = parseGemfile(`
174source "https://rubygems.org"
175
176gem "rails", "7.0.0"
177gem "puma"
178
179group :development, :test do
180 gem "rspec"
181end
182`);
183 const rails = deps.find((d) => d.name === "rails")!;
184 expect(rails.versionSpec).toBe("7.0.0");
185 const rspec = deps.find((d) => d.name === "rspec")!;
186 expect(rspec.isDev).toBe(true);
187 });
188});
189
190describe("deps — parseComposerJson", () => {
191 it("parses require + require-dev, skips php", () => {
192 const deps = parseComposerJson(
193 JSON.stringify({
194 require: { php: ">=8.0", "laravel/framework": "^10.0" },
195 "require-dev": { phpunit: "^10.0" },
196 })
197 );
198 expect(deps.find((d) => d.name === "php")).toBeUndefined();
199 expect(deps.find((d) => d.name === "laravel/framework")).toBeDefined();
200 expect(deps.find((d) => d.name === "phpunit")!.isDev).toBe(true);
201 });
202});
203
204describe("deps — parseManifest (dispatch)", () => {
205 it("routes by basename", () => {
206 expect(
207 parseManifest("frontend/package.json", '{"dependencies":{"x":"1"}}')
208 ).toHaveLength(1);
209 expect(parseManifest("some/go.mod", "require foo v1")).toHaveLength(1);
210 expect(parseManifest("README.md", "# hi")).toHaveLength(0);
211 });
212
213 it("swallows parser errors and returns empty", () => {
214 expect(parseManifest("package.json", "not-json")).toEqual([]);
215 });
216});
217
218describe("deps — TOML helpers", () => {
219 const { splitTomlSections, splitTomlArrayItems, pythonRequirementToDep } =
220 __internal;
221
222 it("splits sections by header", () => {
223 const s = splitTomlSections(`[a]
224x = 1
225
226[b]
227y = 2`);
228 expect(s["a"].trim()).toContain("x = 1");
229 expect(s["b"].trim()).toContain("y = 2");
230 });
231
232 it("splits array items respecting quotes", () => {
233 expect(splitTomlArrayItems('"a, b", "c", "d"')).toEqual([
234 '"a, b"',
235 '"c"',
236 '"d"',
237 ]);
238 });
239
240 it("parses Python requirement specifiers", () => {
241 const d = pythonRequirementToDep("requests>=2.0", false)!;
242 expect(d.name).toBe("requests");
243 expect(d.versionSpec).toBe(">=2.0");
244 });
245});
246
247describe("deps — route auth", () => {
248 it("GET /:owner/:repo/dependencies for unknown repo → 404 or 500", async () => {
249 const res = await app.request("/nobody/missing/dependencies");
250 // Route is mounted — either 404 (repo missing) or 500 (no DB in test env)
251 expect([404, 500]).toContain(res.status);
252 });
253
254 it("POST /:owner/:repo/dependencies/reindex without auth → 302 /login", async () => {
255 const res = await app.request("/alice/repo/dependencies/reindex", {
256 method: "POST",
257 });
258 expect(res.status).toBe(302);
259 expect(res.headers.get("location") || "").toContain("/login");
260 });
261});