Skip to content

Commit 848b821

Browse files
AlbinoGeekclaude
andcommitted
test: add json.ts and asyncPool unit tests
Cover jsonRespond, spreadWhen, spreadDefined, truncateLines, truncateText, readPackageVersion, readMcpServerVersion, and asyncPool concurrency/ordering/error behavior. 26 tests total. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4508a41 commit 848b821

File tree

2 files changed

+172
-0
lines changed

2 files changed

+172
-0
lines changed

src/server/github-client.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { describe, expect, test } from "bun:test";
2+
3+
import { asyncPool } from "./github-client.js";
4+
5+
describe("asyncPool", () => {
6+
test("processes all items and returns results", async () => {
7+
const items = [1, 2, 3, 4, 5];
8+
const results = await asyncPool(items, 2, async (n) => n * 10);
9+
// Results may arrive in any order due to concurrency
10+
expect(results.sort((a, b) => a - b)).toEqual([10, 20, 30, 40, 50]);
11+
});
12+
13+
test("respects concurrency limit", async () => {
14+
let running = 0;
15+
let maxRunning = 0;
16+
17+
const items = [1, 2, 3, 4, 5, 6];
18+
await asyncPool(items, 2, async (n) => {
19+
running++;
20+
maxRunning = Math.max(maxRunning, running);
21+
// Simulate async work
22+
await new Promise((resolve) => setTimeout(resolve, 10));
23+
running--;
24+
return n;
25+
});
26+
27+
expect(maxRunning).toBeLessThanOrEqual(2);
28+
});
29+
30+
test("handles empty input", async () => {
31+
const results = await asyncPool([], 4, async (n: number) => n);
32+
expect(results).toEqual([]);
33+
});
34+
35+
test("propagates errors", async () => {
36+
const items = [1, 2, 3];
37+
await expect(
38+
asyncPool(items, 2, async (n) => {
39+
if (n === 2) throw new Error("boom");
40+
return n;
41+
}),
42+
).rejects.toThrow("boom");
43+
});
44+
45+
test("concurrency 1 processes sequentially", async () => {
46+
const order: number[] = [];
47+
const items = [1, 2, 3];
48+
await asyncPool(items, 1, async (n) => {
49+
order.push(n);
50+
await new Promise((resolve) => setTimeout(resolve, 5));
51+
return n;
52+
});
53+
expect(order).toEqual([1, 2, 3]);
54+
});
55+
});

src/server/json.test.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { describe, expect, test } from "bun:test";
2+
3+
import {
4+
jsonRespond,
5+
MCP_JSON_FORMAT_VERSION,
6+
readMcpServerVersion,
7+
readPackageVersion,
8+
spreadDefined,
9+
spreadWhen,
10+
truncateLines,
11+
truncateText,
12+
} from "./json.js";
13+
14+
describe("MCP_JSON_FORMAT_VERSION", () => {
15+
test("is '1'", () => {
16+
expect(MCP_JSON_FORMAT_VERSION).toBe("1");
17+
});
18+
});
19+
20+
describe("readPackageVersion", () => {
21+
test("returns a semver string from package.json", () => {
22+
const v = readPackageVersion();
23+
expect(v).toMatch(/^\d+\.\d+\.\d+/);
24+
});
25+
});
26+
27+
describe("readMcpServerVersion", () => {
28+
test("returns major.minor.patch format", () => {
29+
const v = readMcpServerVersion();
30+
expect(v).toMatch(/^\d+\.\d+\.\d+$/);
31+
});
32+
});
33+
34+
describe("jsonRespond", () => {
35+
test("returns minified JSON", () => {
36+
const result = jsonRespond({ a: 1, b: "two" });
37+
expect(result).toBe('{"a":1,"b":"two"}');
38+
});
39+
40+
test("handles nested objects", () => {
41+
const result = jsonRespond({ x: { y: [1, 2] } });
42+
expect(result).toBe('{"x":{"y":[1,2]}}');
43+
});
44+
45+
test("handles empty object", () => {
46+
expect(jsonRespond({})).toBe("{}");
47+
});
48+
});
49+
50+
describe("spreadWhen", () => {
51+
test("returns fields when condition is true", () => {
52+
const result = { base: 1, ...spreadWhen(true, { extra: 2 }) };
53+
expect(result).toEqual({ base: 1, extra: 2 });
54+
});
55+
56+
test("returns empty object when condition is false", () => {
57+
const result = { base: 1, ...spreadWhen(false, { extra: 2 }) };
58+
expect(result).toEqual({ base: 1 });
59+
});
60+
});
61+
62+
describe("spreadDefined", () => {
63+
test("spreads key when value is defined", () => {
64+
const result = { a: 1, ...spreadDefined("b", 42) };
65+
expect(result).toEqual({ a: 1, b: 42 });
66+
});
67+
68+
test("spreads nothing when value is undefined", () => {
69+
const result = { a: 1, ...spreadDefined("b", undefined) };
70+
expect(result).toEqual({ a: 1 });
71+
});
72+
73+
test("spreads falsy values that are not undefined", () => {
74+
expect({ ...spreadDefined("x", 0) }).toEqual({ x: 0 });
75+
expect({ ...spreadDefined("x", "") }).toEqual({ x: "" });
76+
expect({ ...spreadDefined("x", false) }).toEqual({ x: false });
77+
expect({ ...spreadDefined("x", null) }).toEqual({ x: null });
78+
});
79+
});
80+
81+
describe("truncateLines", () => {
82+
test("returns text unchanged when within limit", () => {
83+
const text = "line1\nline2\nline3";
84+
expect(truncateLines(text, 5)).toBe(text);
85+
});
86+
87+
test("returns text unchanged at exact limit", () => {
88+
const text = "a\nb\nc";
89+
expect(truncateLines(text, 3)).toBe(text);
90+
});
91+
92+
test("truncates and appends notice when over limit", () => {
93+
const text = "1\n2\n3\n4\n5";
94+
const result = truncateLines(text, 2);
95+
expect(result).toBe("1\n2\n... [3 lines truncated]");
96+
});
97+
98+
test("single line over limit keeps first line", () => {
99+
// 1 line, limit 1 — no truncation
100+
expect(truncateLines("only", 1)).toBe("only");
101+
});
102+
});
103+
104+
describe("truncateText", () => {
105+
test("returns text unchanged when within limit", () => {
106+
expect(truncateText("short", 100)).toBe("short");
107+
});
108+
109+
test("returns text unchanged at exact limit", () => {
110+
expect(truncateText("12345", 5)).toBe("12345");
111+
});
112+
113+
test("truncates and appends notice when over limit", () => {
114+
const result = truncateText("abcdef", 3);
115+
expect(result).toBe("abc\n... [truncated]");
116+
});
117+
});

0 commit comments

Comments
 (0)