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

ssh-server.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.

ssh-server.test.tsBlame211 lines · 1 contributor
60323c5Claude1/**
2 * Tests for Block SSH-1 — ssh-server.ts
3 *
4 * We test the pure helpers in isolation (no DB, no real SSH sockets):
5 * - parseGitCommand — command string → {service, owner, repo}
6 * - computePushedRefs — before/after show-ref → PushRef[]
7 *
8 * resolveUserByKeyBlob is DB-dependent; we cover it with a DB-skip guard.
9 */
10
11import { describe, expect, test, beforeEach, mock } from "bun:test";
12import {
13 parseGitCommand,
14 computePushedRefs,
15 resolveUserByKeyBlob,
16} from "../lib/ssh-server";
17
18// ---------------------------------------------------------------------------
19// parseGitCommand
20// ---------------------------------------------------------------------------
21
22describe("parseGitCommand", () => {
23 test("upload-pack with leading slash", () => {
24 const r = parseGitCommand("git-upload-pack '/alice/myrepo.git'");
25 expect(r).toEqual({
26 service: "git-upload-pack",
27 owner: "alice",
28 repo: "myrepo",
29 });
30 });
31
32 test("receive-pack with leading slash", () => {
33 const r = parseGitCommand("git-receive-pack '/bob/project.git'");
34 expect(r).toEqual({
35 service: "git-receive-pack",
36 owner: "bob",
37 repo: "project",
38 });
39 });
40
41 test("upload-pack without leading slash (some clients omit it)", () => {
42 const r = parseGitCommand("git-upload-pack 'alice/myrepo.git'");
43 expect(r).toEqual({
44 service: "git-upload-pack",
45 owner: "alice",
46 repo: "myrepo",
47 });
48 });
49
50 test("upload-pack without quotes (rare but valid)", () => {
51 const r = parseGitCommand("git-upload-pack /alice/myrepo.git");
52 expect(r).toEqual({
53 service: "git-upload-pack",
54 owner: "alice",
55 repo: "myrepo",
56 });
57 });
58
59 test(".git suffix is stripped", () => {
60 const r = parseGitCommand("git-upload-pack '/alice/myrepo.git'");
61 expect(r?.repo).toBe("myrepo");
62 });
63
64 test("repo without .git suffix is accepted", () => {
65 const r = parseGitCommand("git-upload-pack '/alice/myrepo'");
66 expect(r?.repo).toBe("myrepo");
67 });
68
69 test("repo with dots and hyphens in name", () => {
70 const r = parseGitCommand("git-upload-pack '/alice/my-repo.v2'");
71 expect(r).toEqual({
72 service: "git-upload-pack",
73 owner: "alice",
74 repo: "my-repo.v2",
75 });
76 });
77
78 test("trailing whitespace is ignored", () => {
79 const r = parseGitCommand("git-upload-pack '/alice/repo.git' ");
80 expect(r?.owner).toBe("alice");
81 });
82
83 test("unknown service returns null", () => {
84 expect(parseGitCommand("bash -i")).toBeNull();
85 });
86
87 test("empty string returns null", () => {
88 expect(parseGitCommand("")).toBeNull();
89 });
90
91 test("missing owner/repo returns null", () => {
92 expect(parseGitCommand("git-upload-pack")).toBeNull();
93 });
94
95 test("path traversal attempt returns null", () => {
96 expect(
97 parseGitCommand("git-upload-pack '/../../etc/passwd'")
98 ).toBeNull();
99 });
100
101 test("shell injection attempt returns null", () => {
102 expect(
103 parseGitCommand("git-upload-pack '/alice/repo'; rm -rf /")
104 ).toBeNull();
105 });
106});
107
108// ---------------------------------------------------------------------------
109// computePushedRefs
110// ---------------------------------------------------------------------------
111
112describe("computePushedRefs", () => {
113 const sha1 = "a".repeat(40);
114 const sha2 = "b".repeat(40);
115 const sha3 = "c".repeat(40);
116 const ZERO = "0".repeat(40);
117
118 test("new branch is reported with ZERO oldSha", () => {
119 const refs = computePushedRefs(
120 [],
121 [{ sha: sha1, ref: "refs/heads/main" }]
122 );
123 expect(refs).toEqual([
124 { oldSha: ZERO, newSha: sha1, refName: "refs/heads/main" },
125 ]);
126 });
127
128 test("updated branch reports old and new sha", () => {
129 const refs = computePushedRefs(
130 [{ sha: sha1, ref: "refs/heads/main" }],
131 [{ sha: sha2, ref: "refs/heads/main" }]
132 );
133 expect(refs).toEqual([
134 { oldSha: sha1, newSha: sha2, refName: "refs/heads/main" },
135 ]);
136 });
137
138 test("deleted branch is reported with ZERO newSha", () => {
139 const refs = computePushedRefs(
140 [{ sha: sha1, ref: "refs/heads/feature" }],
141 []
142 );
143 expect(refs).toEqual([
144 { oldSha: sha1, newSha: ZERO, refName: "refs/heads/feature" },
145 ]);
146 });
147
148 test("unchanged refs are not reported", () => {
149 const refs = computePushedRefs(
150 [
151 { sha: sha1, ref: "refs/heads/main" },
152 { sha: sha2, ref: "refs/heads/stable" },
153 ],
154 [
155 { sha: sha1, ref: "refs/heads/main" },
156 { sha: sha2, ref: "refs/heads/stable" },
157 ]
158 );
159 expect(refs).toHaveLength(0);
160 });
161
162 test("multiple changes in one push", () => {
163 const refs = computePushedRefs(
164 [
165 { sha: sha1, ref: "refs/heads/main" },
166 { sha: sha2, ref: "refs/heads/old" },
167 ],
168 [
169 { sha: sha3, ref: "refs/heads/main" },
170 { sha: sha1, ref: "refs/heads/new" },
171 ]
172 );
173 // main updated, old deleted, new created
174 expect(refs).toHaveLength(3);
175 const main = refs.find((r) => r.refName === "refs/heads/main");
176 expect(main).toEqual({
177 oldSha: sha1,
178 newSha: sha3,
179 refName: "refs/heads/main",
180 });
181 const deleted = refs.find((r) => r.refName === "refs/heads/old");
182 expect(deleted?.newSha).toBe(ZERO);
183 const created = refs.find((r) => r.refName === "refs/heads/new");
184 expect(created?.oldSha).toBe(ZERO);
185 });
186
187 test("empty before and after is empty", () => {
188 expect(computePushedRefs([], [])).toHaveLength(0);
189 });
190
191 test("tag push is included", () => {
192 const refs = computePushedRefs(
193 [],
194 [{ sha: sha1, ref: "refs/tags/v1.0.0" }]
195 );
196 expect(refs[0]?.refName).toBe("refs/tags/v1.0.0");
197 });
198});
199
200// ---------------------------------------------------------------------------
201// resolveUserByKeyBlob — DB-gated test
202// ---------------------------------------------------------------------------
203
204describe("resolveUserByKeyBlob", () => {
205 test("returns null for an unknown key blob", async () => {
206 const fakeBlob = Buffer.from("not-a-real-key-blob");
207 // If DB is unreachable the function fails closed and returns null.
208 const result = await resolveUserByKeyBlob(fakeBlob);
209 expect(result).toBeNull();
210 });
211});