@inferevents/mcp 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -30
- package/dist/index.js +0 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +0 -8
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/dist/api-client.test.d.ts +0 -2
- package/dist/api-client.test.d.ts.map +0 -1
- package/dist/api-client.test.js +0 -383
- package/dist/api-client.test.js.map +0 -1
- package/dist/charts.test.d.ts +0 -2
- package/dist/charts.test.d.ts.map +0 -1
- package/dist/charts.test.js +0 -218
- package/dist/charts.test.js.map +0 -1
- package/dist/config.test.d.ts +0 -2
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -409
- package/dist/config.test.js.map +0 -1
- package/dist/tips.test.d.ts +0 -2
- package/dist/tips.test.d.ts.map +0 -1
- package/dist/tips.test.js +0 -92
- package/dist/tips.test.js.map +0 -1
- package/dist/tools/get-connection-info.d.ts +0 -4
- package/dist/tools/get-connection-info.d.ts.map +0 -1
- package/dist/tools/get-connection-info.js +0 -22
- package/dist/tools/get-connection-info.js.map +0 -1
package/dist/charts.test.js
DELETED
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { sanitize, bar, sparkline, status, trend, miniBarChart } from "./charts.js";
|
|
3
|
-
describe("sanitize", () => {
|
|
4
|
-
it("returns short strings as-is", () => {
|
|
5
|
-
expect(sanitize("hello world")).toBe("hello world");
|
|
6
|
-
});
|
|
7
|
-
it("truncates strings over 100 chars with ellipsis", () => {
|
|
8
|
-
const long = "a".repeat(120);
|
|
9
|
-
const result = sanitize(long);
|
|
10
|
-
expect(result).toHaveLength(103); // 100 + "..."
|
|
11
|
-
expect(result.endsWith("...")).toBe(true);
|
|
12
|
-
expect(result.startsWith("a".repeat(100))).toBe(true);
|
|
13
|
-
});
|
|
14
|
-
it("returns exactly 100-char strings without truncation", () => {
|
|
15
|
-
const exact = "b".repeat(100);
|
|
16
|
-
expect(sanitize(exact)).toBe(exact);
|
|
17
|
-
});
|
|
18
|
-
it("strips control characters", () => {
|
|
19
|
-
const withControl = "hello\x00world\x08test\x1F";
|
|
20
|
-
expect(sanitize(withControl)).toBe("helloworldtest");
|
|
21
|
-
});
|
|
22
|
-
it("preserves newlines and tabs (allowed whitespace)", () => {
|
|
23
|
-
// \n is \x0A, \t is \x09 — both outside the stripped range
|
|
24
|
-
expect(sanitize("line1\nline2\ttab")).toBe("line1\nline2\ttab");
|
|
25
|
-
});
|
|
26
|
-
it("converts numbers via JSON.stringify", () => {
|
|
27
|
-
expect(sanitize(42)).toBe("42");
|
|
28
|
-
expect(sanitize(3.14)).toBe("3.14");
|
|
29
|
-
});
|
|
30
|
-
it("converts objects via JSON.stringify", () => {
|
|
31
|
-
expect(sanitize({ key: "val" })).toBe('{"key":"val"}');
|
|
32
|
-
});
|
|
33
|
-
it("converts arrays via JSON.stringify", () => {
|
|
34
|
-
expect(sanitize([1, 2, 3])).toBe("[1,2,3]");
|
|
35
|
-
});
|
|
36
|
-
it("handles null", () => {
|
|
37
|
-
expect(sanitize(null)).toBe("null");
|
|
38
|
-
});
|
|
39
|
-
it("handles undefined", () => {
|
|
40
|
-
// JSON.stringify(undefined) returns undefined, ?? "" catches it
|
|
41
|
-
expect(sanitize(undefined)).toBe("");
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
describe("bar", () => {
|
|
45
|
-
it("renders correct filled/empty ratio for 50%", () => {
|
|
46
|
-
const result = bar(50, 100, 20);
|
|
47
|
-
const filled = (result.match(/█/g) || []).length;
|
|
48
|
-
const empty = (result.match(/░/g) || []).length;
|
|
49
|
-
expect(filled).toBe(10);
|
|
50
|
-
expect(empty).toBe(10);
|
|
51
|
-
expect(result).toContain("50%");
|
|
52
|
-
});
|
|
53
|
-
it("renders all empty for 0%", () => {
|
|
54
|
-
const result = bar(0, 100, 20);
|
|
55
|
-
const filled = (result.match(/█/g) || []).length;
|
|
56
|
-
const empty = (result.match(/░/g) || []).length;
|
|
57
|
-
expect(filled).toBe(0);
|
|
58
|
-
expect(empty).toBe(20);
|
|
59
|
-
expect(result).toContain("0%");
|
|
60
|
-
});
|
|
61
|
-
it("renders all filled for 100%", () => {
|
|
62
|
-
const result = bar(100, 100, 20);
|
|
63
|
-
const filled = (result.match(/█/g) || []).length;
|
|
64
|
-
const empty = (result.match(/░/g) || []).length;
|
|
65
|
-
expect(filled).toBe(20);
|
|
66
|
-
expect(empty).toBe(0);
|
|
67
|
-
expect(result).toContain("100%");
|
|
68
|
-
});
|
|
69
|
-
it("handles max=0 gracefully", () => {
|
|
70
|
-
const result = bar(0, 0, 20);
|
|
71
|
-
const empty = (result.match(/░/g) || []).length;
|
|
72
|
-
expect(empty).toBe(20);
|
|
73
|
-
expect(result).toContain("0%");
|
|
74
|
-
});
|
|
75
|
-
it("uses default width of 25", () => {
|
|
76
|
-
const result = bar(100, 100);
|
|
77
|
-
const filled = (result.match(/█/g) || []).length;
|
|
78
|
-
expect(filled).toBe(25);
|
|
79
|
-
});
|
|
80
|
-
it("respects custom width parameter", () => {
|
|
81
|
-
const result = bar(100, 100, 10);
|
|
82
|
-
const filled = (result.match(/█/g) || []).length;
|
|
83
|
-
expect(filled).toBe(10);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
describe("sparkline", () => {
|
|
87
|
-
it("renders sparkline characters for varying values", () => {
|
|
88
|
-
const result = sparkline([1, 3, 5, 2, 8, 6, 7]);
|
|
89
|
-
expect(result).toHaveLength(7);
|
|
90
|
-
// Each character should be a valid sparkline char
|
|
91
|
-
for (const ch of result) {
|
|
92
|
-
expect("▁▂▃▄▅▆▇█").toContain(ch);
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
it("uses lowest char for min and highest char for max", () => {
|
|
96
|
-
const result = sparkline([0, 100]);
|
|
97
|
-
expect(result[0]).toBe("▁");
|
|
98
|
-
expect(result[1]).toBe("█");
|
|
99
|
-
});
|
|
100
|
-
it("returns empty string for empty array", () => {
|
|
101
|
-
expect(sparkline([])).toBe("");
|
|
102
|
-
});
|
|
103
|
-
it("handles single value", () => {
|
|
104
|
-
const result = sparkline([5]);
|
|
105
|
-
expect(result).toHaveLength(1);
|
|
106
|
-
expect("▁▂▃▄▅▆▇█").toContain(result);
|
|
107
|
-
});
|
|
108
|
-
it("handles all same values (flat line)", () => {
|
|
109
|
-
const result = sparkline([5, 5, 5, 5]);
|
|
110
|
-
expect(result).toHaveLength(4);
|
|
111
|
-
// When range is 0, all values map to the same index
|
|
112
|
-
const firstChar = result[0];
|
|
113
|
-
expect(result).toBe(firstChar.repeat(4));
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
describe("status", () => {
|
|
117
|
-
const thresholds = { bad: 10, ok: 20, good: 30 };
|
|
118
|
-
it("returns green circle for values >= good threshold", () => {
|
|
119
|
-
expect(status(30, thresholds)).toBe("🟢");
|
|
120
|
-
expect(status(50, thresholds)).toBe("🟢");
|
|
121
|
-
});
|
|
122
|
-
it("returns yellow circle for values between ok and good", () => {
|
|
123
|
-
expect(status(25, thresholds)).toBe("🟡");
|
|
124
|
-
expect(status(20, thresholds)).toBe("🟡");
|
|
125
|
-
});
|
|
126
|
-
it("returns yellow circle for values between bad and ok", () => {
|
|
127
|
-
expect(status(15, thresholds)).toBe("🟡");
|
|
128
|
-
expect(status(10, thresholds)).toBe("🟡");
|
|
129
|
-
});
|
|
130
|
-
it("returns red circle for values below bad threshold", () => {
|
|
131
|
-
expect(status(5, thresholds)).toBe("🔴");
|
|
132
|
-
expect(status(0, thresholds)).toBe("🔴");
|
|
133
|
-
expect(status(9, thresholds)).toBe("🔴");
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
describe("trend", () => {
|
|
137
|
-
it("shows up arrow with positive percentage", () => {
|
|
138
|
-
const result = trend(38, 29);
|
|
139
|
-
expect(result).toContain("↑");
|
|
140
|
-
expect(result).toContain("+31%");
|
|
141
|
-
});
|
|
142
|
-
it("shows down arrow with negative percentage", () => {
|
|
143
|
-
const result = trend(29, 38);
|
|
144
|
-
expect(result).toContain("↓");
|
|
145
|
-
expect(result).toContain("-24%");
|
|
146
|
-
});
|
|
147
|
-
it('shows flat arrow for <1% change', () => {
|
|
148
|
-
const result = trend(100, 100);
|
|
149
|
-
expect(result).toBe("→ flat");
|
|
150
|
-
});
|
|
151
|
-
it('shows "new" when previous is 0 and current > 0', () => {
|
|
152
|
-
const result = trend(10, 0);
|
|
153
|
-
expect(result).toBe("↑ new");
|
|
154
|
-
});
|
|
155
|
-
it('shows "0" when both are 0', () => {
|
|
156
|
-
const result = trend(0, 0);
|
|
157
|
-
expect(result).toBe("→ 0");
|
|
158
|
-
});
|
|
159
|
-
it("handles very small but non-trivial changes", () => {
|
|
160
|
-
// 1% change exactly: (101 - 100) / 100 * 100 = 1%
|
|
161
|
-
const result = trend(101, 100);
|
|
162
|
-
expect(result).toContain("↑");
|
|
163
|
-
expect(result).toContain("+1%");
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
describe("miniBarChart", () => {
|
|
167
|
-
it("renders grouped bars with labels and percentages", () => {
|
|
168
|
-
const result = miniBarChart([
|
|
169
|
-
{ key: "US", count: 450 },
|
|
170
|
-
{ key: "UK", count: 120 },
|
|
171
|
-
], 20);
|
|
172
|
-
expect(result).toContain("US");
|
|
173
|
-
expect(result).toContain("UK");
|
|
174
|
-
expect(result).toContain("450");
|
|
175
|
-
expect(result).toContain("120");
|
|
176
|
-
// Should have percentage labels
|
|
177
|
-
expect(result).toMatch(/\d+%/);
|
|
178
|
-
});
|
|
179
|
-
it("returns empty string for empty groups", () => {
|
|
180
|
-
expect(miniBarChart([])).toBe("");
|
|
181
|
-
});
|
|
182
|
-
it("separates groups with double newlines", () => {
|
|
183
|
-
const result = miniBarChart([
|
|
184
|
-
{ key: "A", count: 10 },
|
|
185
|
-
{ key: "B", count: 20 },
|
|
186
|
-
]);
|
|
187
|
-
expect(result).toContain("\n\n");
|
|
188
|
-
});
|
|
189
|
-
it("pads keys to align bars", () => {
|
|
190
|
-
const result = miniBarChart([
|
|
191
|
-
{ key: "Short", count: 10 },
|
|
192
|
-
{ key: "LongerKey", count: 20 },
|
|
193
|
-
]);
|
|
194
|
-
const lines = result.split("\n\n");
|
|
195
|
-
// Both lines should have the key padded to the same length before the bar
|
|
196
|
-
const firstKeySection = lines[0].split("█")[0].split("░")[0];
|
|
197
|
-
const secondKeySection = lines[1].split("█")[0].split("░")[0];
|
|
198
|
-
expect(firstKeySection.length).toBe(secondKeySection.length);
|
|
199
|
-
});
|
|
200
|
-
it("handles single group", () => {
|
|
201
|
-
const result = miniBarChart([{ key: "Only", count: 100 }], 10);
|
|
202
|
-
expect(result).toContain("Only");
|
|
203
|
-
expect(result).toContain("100%");
|
|
204
|
-
// Full bar for the only group
|
|
205
|
-
const filled = (result.match(/█/g) || []).length;
|
|
206
|
-
expect(filled).toBe(10);
|
|
207
|
-
});
|
|
208
|
-
it("handles all-zero counts", () => {
|
|
209
|
-
const result = miniBarChart([
|
|
210
|
-
{ key: "A", count: 0 },
|
|
211
|
-
{ key: "B", count: 0 },
|
|
212
|
-
]);
|
|
213
|
-
expect(result).toContain("0%");
|
|
214
|
-
// No filled blocks when max is 0
|
|
215
|
-
expect(result).not.toContain("█");
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
//# sourceMappingURL=charts.test.js.map
|
package/dist/charts.test.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"charts.test.js","sourceRoot":"","sources":["../src/charts.test.ts"],"names":[],"mappings":"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"}
|
package/dist/config.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":""}
|
package/dist/config.test.js
DELETED
|
@@ -1,409 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
// Mock node:fs/promises before any imports that use it
|
|
3
|
-
vi.mock("node:fs/promises", () => ({
|
|
4
|
-
readFile: vi.fn(),
|
|
5
|
-
writeFile: vi.fn(),
|
|
6
|
-
mkdir: vi.fn(),
|
|
7
|
-
}));
|
|
8
|
-
vi.mock("node:os", () => ({
|
|
9
|
-
homedir: () => "/mock/home",
|
|
10
|
-
}));
|
|
11
|
-
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
12
|
-
import { loadConfig, switchProject, listProjects, addProject, readConfigFile, writeConfigFile, ConfigError, } from "./config.js";
|
|
13
|
-
const mockReadFile = vi.mocked(readFile);
|
|
14
|
-
const mockWriteFile = vi.mocked(writeFile);
|
|
15
|
-
const mockMkdir = vi.mocked(mkdir);
|
|
16
|
-
describe("config", () => {
|
|
17
|
-
const originalEnv = { ...process.env };
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
vi.clearAllMocks();
|
|
20
|
-
// Clean env vars before each test
|
|
21
|
-
delete process.env.INFER_API_KEY;
|
|
22
|
-
delete process.env.INFER_ENDPOINT;
|
|
23
|
-
delete process.env.INFER_PROJECT_ID;
|
|
24
|
-
});
|
|
25
|
-
afterEach(() => {
|
|
26
|
-
// Restore original env
|
|
27
|
-
process.env = { ...originalEnv };
|
|
28
|
-
});
|
|
29
|
-
describe("loadConfig with env vars", () => {
|
|
30
|
-
it("uses INFER_API_KEY when set", async () => {
|
|
31
|
-
process.env.INFER_API_KEY = "pk_read_envkey";
|
|
32
|
-
const config = await loadConfig();
|
|
33
|
-
expect(config.apiKey).toBe("pk_read_envkey");
|
|
34
|
-
});
|
|
35
|
-
it("uses INFER_ENDPOINT when set", async () => {
|
|
36
|
-
process.env.INFER_API_KEY = "pk_read_envkey";
|
|
37
|
-
process.env.INFER_ENDPOINT = "https://custom.endpoint.com";
|
|
38
|
-
const config = await loadConfig();
|
|
39
|
-
expect(config.endpoint).toBe("https://custom.endpoint.com");
|
|
40
|
-
});
|
|
41
|
-
it("uses INFER_PROJECT_ID when set", async () => {
|
|
42
|
-
process.env.INFER_API_KEY = "pk_read_envkey";
|
|
43
|
-
process.env.INFER_PROJECT_ID = "proj_env123";
|
|
44
|
-
const config = await loadConfig();
|
|
45
|
-
expect(config.projectId).toBe("proj_env123");
|
|
46
|
-
});
|
|
47
|
-
it("defaults endpoint when INFER_ENDPOINT not set", async () => {
|
|
48
|
-
process.env.INFER_API_KEY = "pk_read_envkey";
|
|
49
|
-
const config = await loadConfig();
|
|
50
|
-
expect(config.endpoint).toBe("https://api.infer.events");
|
|
51
|
-
});
|
|
52
|
-
it("skips file reading when env key is set", async () => {
|
|
53
|
-
process.env.INFER_API_KEY = "pk_read_envkey";
|
|
54
|
-
await loadConfig();
|
|
55
|
-
expect(mockReadFile).not.toHaveBeenCalled();
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
describe("loadConfig with config file", () => {
|
|
59
|
-
it("reads from ~/.infer/config.json", async () => {
|
|
60
|
-
mockReadFile.mockResolvedValueOnce(JSON.stringify({
|
|
61
|
-
apiKey: "pk_read_filekey",
|
|
62
|
-
endpoint: "https://api.infer.events",
|
|
63
|
-
projectId: "proj_file",
|
|
64
|
-
}));
|
|
65
|
-
await loadConfig();
|
|
66
|
-
expect(mockReadFile).toHaveBeenCalledWith("/mock/home/.infer/config.json", "utf-8");
|
|
67
|
-
});
|
|
68
|
-
it("handles old flat format { apiKey, endpoint, projectId }", async () => {
|
|
69
|
-
mockReadFile.mockResolvedValueOnce(JSON.stringify({
|
|
70
|
-
apiKey: "pk_read_oldformat",
|
|
71
|
-
endpoint: "https://old.api.com",
|
|
72
|
-
projectId: "proj_old",
|
|
73
|
-
}));
|
|
74
|
-
const config = await loadConfig();
|
|
75
|
-
expect(config.apiKey).toBe("pk_read_oldformat");
|
|
76
|
-
expect(config.endpoint).toBe("https://old.api.com");
|
|
77
|
-
expect(config.projectId).toBe("proj_old");
|
|
78
|
-
});
|
|
79
|
-
it("strips trailing slashes from endpoint in old format", async () => {
|
|
80
|
-
mockReadFile.mockResolvedValueOnce(JSON.stringify({
|
|
81
|
-
apiKey: "pk_read_test",
|
|
82
|
-
endpoint: "https://api.example.com///",
|
|
83
|
-
}));
|
|
84
|
-
const config = await loadConfig();
|
|
85
|
-
expect(config.endpoint).toBe("https://api.example.com");
|
|
86
|
-
});
|
|
87
|
-
it("handles new profiles format", async () => {
|
|
88
|
-
mockReadFile.mockImplementation(async (path) => {
|
|
89
|
-
if (typeof path === "string" && path.endsWith("config.json")) {
|
|
90
|
-
return JSON.stringify({
|
|
91
|
-
endpoint: "https://api.infer.events",
|
|
92
|
-
activeProject: "my-app",
|
|
93
|
-
projects: {
|
|
94
|
-
"my-app": {
|
|
95
|
-
apiKey: "pk_read_myapp",
|
|
96
|
-
projectId: "proj_myapp",
|
|
97
|
-
},
|
|
98
|
-
"other-app": {
|
|
99
|
-
apiKey: "pk_read_other",
|
|
100
|
-
projectId: "proj_other",
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
throw new Error("ENOENT");
|
|
106
|
-
});
|
|
107
|
-
const config = await loadConfig();
|
|
108
|
-
expect(config.apiKey).toBe("pk_read_myapp");
|
|
109
|
-
expect(config.projectId).toBe("proj_myapp");
|
|
110
|
-
});
|
|
111
|
-
it("uses first project when activeProject not set", async () => {
|
|
112
|
-
mockReadFile.mockImplementation(async (path) => {
|
|
113
|
-
if (typeof path === "string" && path.endsWith("config.json")) {
|
|
114
|
-
return JSON.stringify({
|
|
115
|
-
projects: {
|
|
116
|
-
"first-proj": {
|
|
117
|
-
apiKey: "pk_read_first",
|
|
118
|
-
projectId: "proj_first",
|
|
119
|
-
},
|
|
120
|
-
"second-proj": {
|
|
121
|
-
apiKey: "pk_read_second",
|
|
122
|
-
projectId: "proj_second",
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
throw new Error("ENOENT");
|
|
128
|
-
});
|
|
129
|
-
const config = await loadConfig();
|
|
130
|
-
expect(config.apiKey).toBe("pk_read_first");
|
|
131
|
-
expect(config.projectId).toBe("proj_first");
|
|
132
|
-
});
|
|
133
|
-
it("throws ConfigError when config file is missing", async () => {
|
|
134
|
-
mockReadFile.mockRejectedValueOnce(new Error("ENOENT"));
|
|
135
|
-
await expect(loadConfig()).rejects.toThrow(ConfigError);
|
|
136
|
-
await expect(
|
|
137
|
-
// Need to re-mock since the first call consumed the mock
|
|
138
|
-
(mockReadFile.mockRejectedValueOnce(new Error("ENOENT")), loadConfig())).rejects.toThrow(/not configured/);
|
|
139
|
-
});
|
|
140
|
-
it("throws ConfigError when JSON is invalid", async () => {
|
|
141
|
-
mockReadFile.mockResolvedValueOnce("not valid json {{{");
|
|
142
|
-
await expect(loadConfig()).rejects.toThrow(ConfigError);
|
|
143
|
-
});
|
|
144
|
-
it("throws ConfigError when JSON is invalid with descriptive message", async () => {
|
|
145
|
-
mockReadFile.mockResolvedValueOnce("not valid json {{{");
|
|
146
|
-
await expect(loadConfig()).rejects.toThrow(/Invalid JSON/);
|
|
147
|
-
});
|
|
148
|
-
it('throws ConfigError when apiKey does not start with pk_read_', async () => {
|
|
149
|
-
mockReadFile.mockResolvedValueOnce(JSON.stringify({
|
|
150
|
-
apiKey: "pk_write_wrongtype",
|
|
151
|
-
endpoint: "https://api.infer.events",
|
|
152
|
-
}));
|
|
153
|
-
await expect(loadConfig()).rejects.toThrow(ConfigError);
|
|
154
|
-
mockReadFile.mockResolvedValueOnce(JSON.stringify({
|
|
155
|
-
apiKey: "pk_write_wrongtype",
|
|
156
|
-
endpoint: "https://api.infer.events",
|
|
157
|
-
}));
|
|
158
|
-
await expect(loadConfig()).rejects.toThrow(/read API key/);
|
|
159
|
-
});
|
|
160
|
-
it("throws ConfigError when no projects exist in profiles format", async () => {
|
|
161
|
-
mockReadFile.mockResolvedValueOnce(JSON.stringify({
|
|
162
|
-
projects: {},
|
|
163
|
-
}));
|
|
164
|
-
await expect(loadConfig()).rejects.toThrow(ConfigError);
|
|
165
|
-
});
|
|
166
|
-
it('throws ConfigError when project apiKey does not start with pk_read_', async () => {
|
|
167
|
-
mockReadFile.mockImplementation(async (path) => {
|
|
168
|
-
if (typeof path === "string" && path.endsWith("config.json")) {
|
|
169
|
-
return JSON.stringify({
|
|
170
|
-
activeProject: "bad-proj",
|
|
171
|
-
projects: {
|
|
172
|
-
"bad-proj": {
|
|
173
|
-
apiKey: "sk_live_wrongprefix",
|
|
174
|
-
projectId: "proj_bad",
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
throw new Error("ENOENT");
|
|
180
|
-
});
|
|
181
|
-
await expect(loadConfig()).rejects.toThrow(ConfigError);
|
|
182
|
-
await expect(loadConfig()).rejects.toThrow(/Invalid read key/);
|
|
183
|
-
});
|
|
184
|
-
it("defaults endpoint to https://api.infer.events in old format", async () => {
|
|
185
|
-
mockReadFile.mockResolvedValueOnce(JSON.stringify({
|
|
186
|
-
apiKey: "pk_read_noendpoint",
|
|
187
|
-
}));
|
|
188
|
-
const config = await loadConfig();
|
|
189
|
-
expect(config.endpoint).toBe("https://api.infer.events");
|
|
190
|
-
});
|
|
191
|
-
it("defaults endpoint in profiles format", async () => {
|
|
192
|
-
mockReadFile.mockImplementation(async (path) => {
|
|
193
|
-
if (typeof path === "string" && path.endsWith("config.json")) {
|
|
194
|
-
return JSON.stringify({
|
|
195
|
-
activeProject: "proj",
|
|
196
|
-
projects: {
|
|
197
|
-
proj: {
|
|
198
|
-
apiKey: "pk_read_test",
|
|
199
|
-
projectId: "proj_1",
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
throw new Error("ENOENT");
|
|
205
|
-
});
|
|
206
|
-
const config = await loadConfig();
|
|
207
|
-
expect(config.endpoint).toBe("https://api.infer.events");
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
describe("project management", () => {
|
|
211
|
-
describe("switchProject", () => {
|
|
212
|
-
it("changes activeProject and returns config", async () => {
|
|
213
|
-
const baseConfig = {
|
|
214
|
-
endpoint: "https://api.infer.events",
|
|
215
|
-
activeProject: "proj-a",
|
|
216
|
-
projects: {
|
|
217
|
-
"proj-a": { apiKey: "pk_read_a", projectId: "proj_a" },
|
|
218
|
-
"proj-b": { apiKey: "pk_read_b", projectId: "proj_b" },
|
|
219
|
-
},
|
|
220
|
-
};
|
|
221
|
-
// First call: readConfigFile reads original config
|
|
222
|
-
// Second call: loadConfig reads the updated config (after writeConfigFile)
|
|
223
|
-
mockReadFile
|
|
224
|
-
.mockResolvedValueOnce(JSON.stringify(baseConfig))
|
|
225
|
-
.mockResolvedValueOnce(JSON.stringify({ ...baseConfig, activeProject: "proj-b" }));
|
|
226
|
-
mockWriteFile.mockResolvedValue(undefined);
|
|
227
|
-
mockMkdir.mockResolvedValue(undefined);
|
|
228
|
-
const config = await switchProject("proj-b");
|
|
229
|
-
// Should have written the updated config
|
|
230
|
-
expect(mockWriteFile).toHaveBeenCalled();
|
|
231
|
-
const writtenContent = JSON.parse(mockWriteFile.mock.calls[0][1]);
|
|
232
|
-
expect(writtenContent.activeProject).toBe("proj-b");
|
|
233
|
-
// Should return the loaded config for proj-b
|
|
234
|
-
expect(config.apiKey).toBe("pk_read_b");
|
|
235
|
-
expect(config.projectId).toBe("proj_b");
|
|
236
|
-
});
|
|
237
|
-
it("throws ConfigError for unknown project", async () => {
|
|
238
|
-
mockReadFile.mockResolvedValue(JSON.stringify({
|
|
239
|
-
activeProject: "proj-a",
|
|
240
|
-
projects: {
|
|
241
|
-
"proj-a": { apiKey: "pk_read_a", projectId: "proj_a" },
|
|
242
|
-
},
|
|
243
|
-
}));
|
|
244
|
-
await expect(switchProject("nonexistent")).rejects.toThrow(ConfigError);
|
|
245
|
-
await expect(switchProject("nonexistent")).rejects.toThrow(/not found/);
|
|
246
|
-
});
|
|
247
|
-
it("lists available projects in error message", async () => {
|
|
248
|
-
mockReadFile.mockResolvedValue(JSON.stringify({
|
|
249
|
-
projects: {
|
|
250
|
-
alpha: { apiKey: "pk_read_a", projectId: "proj_a" },
|
|
251
|
-
beta: { apiKey: "pk_read_b", projectId: "proj_b" },
|
|
252
|
-
},
|
|
253
|
-
}));
|
|
254
|
-
await expect(switchProject("gamma")).rejects.toThrow(/alpha/);
|
|
255
|
-
await expect(switchProject("gamma")).rejects.toThrow(/beta/);
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
describe("listProjects", () => {
|
|
259
|
-
it("returns all projects with active flag", async () => {
|
|
260
|
-
mockReadFile.mockResolvedValueOnce(JSON.stringify({
|
|
261
|
-
activeProject: "prod",
|
|
262
|
-
projects: {
|
|
263
|
-
prod: { apiKey: "pk_read_prod", projectId: "proj_prod" },
|
|
264
|
-
staging: { apiKey: "pk_read_stg", projectId: "proj_stg" },
|
|
265
|
-
},
|
|
266
|
-
}));
|
|
267
|
-
const projects = await listProjects();
|
|
268
|
-
expect(projects).toHaveLength(2);
|
|
269
|
-
expect(projects).toContainEqual({
|
|
270
|
-
name: "prod",
|
|
271
|
-
projectId: "proj_prod",
|
|
272
|
-
active: true,
|
|
273
|
-
});
|
|
274
|
-
expect(projects).toContainEqual({
|
|
275
|
-
name: "staging",
|
|
276
|
-
projectId: "proj_stg",
|
|
277
|
-
active: false,
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
it("returns empty array when no projects", async () => {
|
|
281
|
-
mockReadFile.mockResolvedValueOnce(JSON.stringify({ projects: {} }));
|
|
282
|
-
const projects = await listProjects();
|
|
283
|
-
expect(projects).toEqual([]);
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
describe("addProject", () => {
|
|
287
|
-
it("adds to config and sets active by default", async () => {
|
|
288
|
-
mockReadFile.mockResolvedValue(JSON.stringify({
|
|
289
|
-
activeProject: "existing",
|
|
290
|
-
projects: {
|
|
291
|
-
existing: { apiKey: "pk_read_ex", projectId: "proj_ex" },
|
|
292
|
-
},
|
|
293
|
-
}));
|
|
294
|
-
mockWriteFile.mockResolvedValue(undefined);
|
|
295
|
-
mockMkdir.mockResolvedValue(undefined);
|
|
296
|
-
await addProject("new-proj", "pk_read_new", "proj_new");
|
|
297
|
-
expect(mockWriteFile).toHaveBeenCalled();
|
|
298
|
-
const written = JSON.parse(mockWriteFile.mock.calls[0][1]);
|
|
299
|
-
expect(written.projects["new-proj"]).toEqual({
|
|
300
|
-
apiKey: "pk_read_new",
|
|
301
|
-
projectId: "proj_new",
|
|
302
|
-
});
|
|
303
|
-
expect(written.activeProject).toBe("new-proj");
|
|
304
|
-
});
|
|
305
|
-
it("adds without setting active when setActive=false", async () => {
|
|
306
|
-
mockReadFile.mockResolvedValue(JSON.stringify({
|
|
307
|
-
activeProject: "existing",
|
|
308
|
-
projects: {
|
|
309
|
-
existing: { apiKey: "pk_read_ex", projectId: "proj_ex" },
|
|
310
|
-
},
|
|
311
|
-
}));
|
|
312
|
-
mockWriteFile.mockResolvedValue(undefined);
|
|
313
|
-
mockMkdir.mockResolvedValue(undefined);
|
|
314
|
-
await addProject("new-proj", "pk_read_new", "proj_new", undefined, false);
|
|
315
|
-
const written = JSON.parse(mockWriteFile.mock.calls[0][1]);
|
|
316
|
-
expect(written.projects["new-proj"]).toBeDefined();
|
|
317
|
-
expect(written.activeProject).toBe("existing");
|
|
318
|
-
});
|
|
319
|
-
it("stores writeKey when provided", async () => {
|
|
320
|
-
mockReadFile.mockResolvedValue(JSON.stringify({
|
|
321
|
-
projects: {},
|
|
322
|
-
}));
|
|
323
|
-
mockWriteFile.mockResolvedValue(undefined);
|
|
324
|
-
mockMkdir.mockResolvedValue(undefined);
|
|
325
|
-
await addProject("proj", "pk_read_key", "proj_id", "pk_write_key");
|
|
326
|
-
const written = JSON.parse(mockWriteFile.mock.calls[0][1]);
|
|
327
|
-
expect(written.projects["proj"].writeKey).toBe("pk_write_key");
|
|
328
|
-
});
|
|
329
|
-
it("stores session when provided", async () => {
|
|
330
|
-
mockReadFile.mockResolvedValue(JSON.stringify({
|
|
331
|
-
projects: {},
|
|
332
|
-
}));
|
|
333
|
-
mockWriteFile.mockResolvedValue(undefined);
|
|
334
|
-
mockMkdir.mockResolvedValue(undefined);
|
|
335
|
-
await addProject("proj", "pk_read_key", "proj_id", undefined, true, "sess_123");
|
|
336
|
-
const written = JSON.parse(mockWriteFile.mock.calls[0][1]);
|
|
337
|
-
expect(written.session).toBe("sess_123");
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
describe("readConfigFile", () => {
|
|
342
|
-
it("migrates old flat format to profiles format", async () => {
|
|
343
|
-
mockReadFile.mockResolvedValueOnce(JSON.stringify({
|
|
344
|
-
apiKey: "pk_read_old",
|
|
345
|
-
projectId: "proj_old",
|
|
346
|
-
session: "sess_old",
|
|
347
|
-
}));
|
|
348
|
-
const config = await readConfigFile();
|
|
349
|
-
expect(config.activeProject).toBe("default");
|
|
350
|
-
expect(config.projects.default).toEqual({
|
|
351
|
-
apiKey: "pk_read_old",
|
|
352
|
-
projectId: "proj_old",
|
|
353
|
-
writeKey: undefined,
|
|
354
|
-
});
|
|
355
|
-
expect(config.session).toBe("sess_old");
|
|
356
|
-
});
|
|
357
|
-
it("returns empty config when file not found", async () => {
|
|
358
|
-
mockReadFile.mockRejectedValueOnce(new Error("ENOENT"));
|
|
359
|
-
const config = await readConfigFile();
|
|
360
|
-
expect(config.projects).toEqual({});
|
|
361
|
-
expect(config.endpoint).toBe("https://api.infer.events");
|
|
362
|
-
});
|
|
363
|
-
});
|
|
364
|
-
describe("writeConfigFile", () => {
|
|
365
|
-
it("creates directory and writes config", async () => {
|
|
366
|
-
mockMkdir.mockResolvedValue(undefined);
|
|
367
|
-
mockWriteFile.mockResolvedValue(undefined);
|
|
368
|
-
await writeConfigFile({
|
|
369
|
-
activeProject: "test",
|
|
370
|
-
projects: {
|
|
371
|
-
test: { apiKey: "pk_read_test", projectId: "proj_test" },
|
|
372
|
-
},
|
|
373
|
-
});
|
|
374
|
-
expect(mockMkdir).toHaveBeenCalledWith("/mock/home/.infer", {
|
|
375
|
-
recursive: true,
|
|
376
|
-
});
|
|
377
|
-
expect(mockWriteFile).toHaveBeenCalledWith("/mock/home/.infer/config.json", expect.any(String), "utf-8");
|
|
378
|
-
});
|
|
379
|
-
it("writes valid JSON with trailing newline", async () => {
|
|
380
|
-
mockMkdir.mockResolvedValue(undefined);
|
|
381
|
-
mockWriteFile.mockResolvedValue(undefined);
|
|
382
|
-
const input = {
|
|
383
|
-
activeProject: "test",
|
|
384
|
-
projects: {
|
|
385
|
-
test: { apiKey: "pk_read_test", projectId: "proj_test" },
|
|
386
|
-
},
|
|
387
|
-
};
|
|
388
|
-
await writeConfigFile(input);
|
|
389
|
-
const written = mockWriteFile.mock.calls[0][1];
|
|
390
|
-
expect(written.endsWith("\n")).toBe(true);
|
|
391
|
-
expect(JSON.parse(written)).toMatchObject(input);
|
|
392
|
-
});
|
|
393
|
-
});
|
|
394
|
-
describe("ConfigError", () => {
|
|
395
|
-
it('has name "ConfigError"', () => {
|
|
396
|
-
const err = new ConfigError("test");
|
|
397
|
-
expect(err.name).toBe("ConfigError");
|
|
398
|
-
});
|
|
399
|
-
it("extends Error", () => {
|
|
400
|
-
const err = new ConfigError("test");
|
|
401
|
-
expect(err).toBeInstanceOf(Error);
|
|
402
|
-
});
|
|
403
|
-
it("stores message", () => {
|
|
404
|
-
const err = new ConfigError("something broke");
|
|
405
|
-
expect(err.message).toBe("something broke");
|
|
406
|
-
});
|
|
407
|
-
});
|
|
408
|
-
});
|
|
409
|
-
//# sourceMappingURL=config.test.js.map
|