@inferevents/mcp 0.3.2 → 0.3.3

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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=api-client.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.test.d.ts","sourceRoot":"","sources":["../src/api-client.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,383 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { ApiClient, ApiError } from "./api-client.js";
3
+ const mockFetch = vi.fn();
4
+ vi.stubGlobal("fetch", mockFetch);
5
+ function makeConfig(overrides) {
6
+ return {
7
+ apiKey: "pk_read_test123",
8
+ endpoint: "https://api.infer.events",
9
+ projectId: "proj_default",
10
+ ...overrides,
11
+ };
12
+ }
13
+ function jsonResponse(data, status = 200) {
14
+ return {
15
+ ok: status >= 200 && status < 300,
16
+ status,
17
+ json: () => Promise.resolve(data),
18
+ text: () => Promise.resolve(JSON.stringify(data)),
19
+ };
20
+ }
21
+ function textResponse(body, status) {
22
+ return {
23
+ ok: false,
24
+ status,
25
+ json: () => Promise.reject(new Error("not json")),
26
+ text: () => Promise.resolve(body),
27
+ };
28
+ }
29
+ describe("ApiClient", () => {
30
+ beforeEach(() => {
31
+ mockFetch.mockReset();
32
+ });
33
+ afterEach(() => {
34
+ mockFetch.mockReset();
35
+ });
36
+ describe("constructor", () => {
37
+ it("sets base URL from config", () => {
38
+ const client = new ApiClient(makeConfig({ endpoint: "https://custom.api.com" }));
39
+ mockFetch.mockResolvedValueOnce(jsonResponse({ groups: [], total: 0 }));
40
+ client.getEventCounts({ eventName: "test", timeRange: "7d" });
41
+ // We'll verify the URL in the fetch call
42
+ expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("https://custom.api.com"), expect.any(Object));
43
+ });
44
+ it("sets auth headers from config apiKey", async () => {
45
+ const client = new ApiClient(makeConfig({ apiKey: "pk_read_mykey" }));
46
+ mockFetch.mockResolvedValueOnce(jsonResponse({ groups: [], total: 0 }));
47
+ await client.getEventCounts({ eventName: "test", timeRange: "7d" });
48
+ expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
49
+ headers: expect.objectContaining({
50
+ Authorization: "Bearer pk_read_mykey",
51
+ "Content-Type": "application/json",
52
+ }),
53
+ }));
54
+ });
55
+ });
56
+ describe("getEventCounts", () => {
57
+ it("calls correct URL with query params", async () => {
58
+ const client = new ApiClient(makeConfig());
59
+ mockFetch.mockResolvedValueOnce(jsonResponse({ groups: [], total: 0 }));
60
+ await client.getEventCounts({ eventName: "signup", timeRange: "7d" });
61
+ const url = new URL(mockFetch.mock.calls[0][0]);
62
+ expect(url.pathname).toBe("/v1/query/event-counts");
63
+ });
64
+ it("includes event_name and time_range", async () => {
65
+ const client = new ApiClient(makeConfig());
66
+ mockFetch.mockResolvedValueOnce(jsonResponse({ groups: [], total: 0 }));
67
+ await client.getEventCounts({ eventName: "signup", timeRange: "30d" });
68
+ const url = new URL(mockFetch.mock.calls[0][0]);
69
+ expect(url.searchParams.get("event_name")).toBe("signup");
70
+ expect(url.searchParams.get("time_range")).toBe("30d");
71
+ });
72
+ it("includes optional group_by", async () => {
73
+ const client = new ApiClient(makeConfig());
74
+ mockFetch.mockResolvedValueOnce(jsonResponse({ groups: [], total: 0 }));
75
+ await client.getEventCounts({
76
+ eventName: "signup",
77
+ timeRange: "7d",
78
+ groupBy: "country",
79
+ });
80
+ const url = new URL(mockFetch.mock.calls[0][0]);
81
+ expect(url.searchParams.get("group_by")).toBe("country");
82
+ });
83
+ it("includes optional filters as JSON", async () => {
84
+ const client = new ApiClient(makeConfig());
85
+ mockFetch.mockResolvedValueOnce(jsonResponse({ groups: [], total: 0 }));
86
+ const filters = [{ field: "plan", op: "eq", value: "pro" }];
87
+ await client.getEventCounts({
88
+ eventName: "signup",
89
+ timeRange: "7d",
90
+ filters,
91
+ });
92
+ const url = new URL(mockFetch.mock.calls[0][0]);
93
+ expect(url.searchParams.get("filters")).toBe(JSON.stringify(filters));
94
+ });
95
+ it("does not include filters when array is empty", async () => {
96
+ const client = new ApiClient(makeConfig());
97
+ mockFetch.mockResolvedValueOnce(jsonResponse({ groups: [], total: 0 }));
98
+ await client.getEventCounts({
99
+ eventName: "signup",
100
+ timeRange: "7d",
101
+ filters: [],
102
+ });
103
+ const url = new URL(mockFetch.mock.calls[0][0]);
104
+ expect(url.searchParams.has("filters")).toBe(false);
105
+ });
106
+ it("includes project_id from params when provided", async () => {
107
+ const client = new ApiClient(makeConfig());
108
+ mockFetch.mockResolvedValueOnce(jsonResponse({ groups: [], total: 0 }));
109
+ await client.getEventCounts({
110
+ eventName: "signup",
111
+ timeRange: "7d",
112
+ projectId: "proj_override",
113
+ });
114
+ const url = new URL(mockFetch.mock.calls[0][0]);
115
+ expect(url.searchParams.get("project_id")).toBe("proj_override");
116
+ });
117
+ it("falls back to default project_id from config", async () => {
118
+ const client = new ApiClient(makeConfig({ projectId: "proj_default" }));
119
+ mockFetch.mockResolvedValueOnce(jsonResponse({ groups: [], total: 0 }));
120
+ await client.getEventCounts({ eventName: "signup", timeRange: "7d" });
121
+ const url = new URL(mockFetch.mock.calls[0][0]);
122
+ expect(url.searchParams.get("project_id")).toBe("proj_default");
123
+ });
124
+ });
125
+ describe("getRetention", () => {
126
+ it("calls correct URL with all required params", async () => {
127
+ const client = new ApiClient(makeConfig());
128
+ mockFetch.mockResolvedValueOnce(jsonResponse({ cohorts: [] }));
129
+ await client.getRetention({
130
+ startEvent: "signup",
131
+ returnEvent: "login",
132
+ timeRange: "30d",
133
+ granularity: "week",
134
+ });
135
+ const url = new URL(mockFetch.mock.calls[0][0]);
136
+ expect(url.pathname).toBe("/v1/query/retention");
137
+ expect(url.searchParams.get("start_event")).toBe("signup");
138
+ expect(url.searchParams.get("return_event")).toBe("login");
139
+ expect(url.searchParams.get("time_range")).toBe("30d");
140
+ expect(url.searchParams.get("granularity")).toBe("week");
141
+ });
142
+ it("includes project_id", async () => {
143
+ const client = new ApiClient(makeConfig({ projectId: "proj_ret" }));
144
+ mockFetch.mockResolvedValueOnce(jsonResponse({ cohorts: [] }));
145
+ await client.getRetention({
146
+ startEvent: "signup",
147
+ returnEvent: "login",
148
+ timeRange: "30d",
149
+ granularity: "week",
150
+ });
151
+ const url = new URL(mockFetch.mock.calls[0][0]);
152
+ expect(url.searchParams.get("project_id")).toBe("proj_ret");
153
+ });
154
+ it("uses param projectId over default", async () => {
155
+ const client = new ApiClient(makeConfig({ projectId: "proj_default" }));
156
+ mockFetch.mockResolvedValueOnce(jsonResponse({ cohorts: [] }));
157
+ await client.getRetention({
158
+ startEvent: "signup",
159
+ returnEvent: "login",
160
+ timeRange: "30d",
161
+ granularity: "week",
162
+ projectId: "proj_override",
163
+ });
164
+ const url = new URL(mockFetch.mock.calls[0][0]);
165
+ expect(url.searchParams.get("project_id")).toBe("proj_override");
166
+ });
167
+ });
168
+ describe("getUserJourney", () => {
169
+ it("calls correct URL with user_id", async () => {
170
+ const client = new ApiClient(makeConfig());
171
+ mockFetch.mockResolvedValueOnce(jsonResponse({ events: [] }));
172
+ await client.getUserJourney({ userId: "user_abc" });
173
+ const url = new URL(mockFetch.mock.calls[0][0]);
174
+ expect(url.pathname).toBe("/v1/query/user-journey");
175
+ expect(url.searchParams.get("user_id")).toBe("user_abc");
176
+ });
177
+ it("includes optional time_range", async () => {
178
+ const client = new ApiClient(makeConfig());
179
+ mockFetch.mockResolvedValueOnce(jsonResponse({ events: [] }));
180
+ await client.getUserJourney({ userId: "user_abc", timeRange: "7d" });
181
+ const url = new URL(mockFetch.mock.calls[0][0]);
182
+ expect(url.searchParams.get("time_range")).toBe("7d");
183
+ });
184
+ it("includes optional limit", async () => {
185
+ const client = new ApiClient(makeConfig());
186
+ mockFetch.mockResolvedValueOnce(jsonResponse({ events: [] }));
187
+ await client.getUserJourney({ userId: "user_abc", limit: 50 });
188
+ const url = new URL(mockFetch.mock.calls[0][0]);
189
+ expect(url.searchParams.get("limit")).toBe("50");
190
+ });
191
+ it("does not include time_range or limit when not provided", async () => {
192
+ const client = new ApiClient(makeConfig());
193
+ mockFetch.mockResolvedValueOnce(jsonResponse({ events: [] }));
194
+ await client.getUserJourney({ userId: "user_abc" });
195
+ const url = new URL(mockFetch.mock.calls[0][0]);
196
+ expect(url.searchParams.has("time_range")).toBe(false);
197
+ expect(url.searchParams.has("limit")).toBe(false);
198
+ });
199
+ });
200
+ describe("getTopEvents", () => {
201
+ it("calls correct URL with time_range", async () => {
202
+ const client = new ApiClient(makeConfig());
203
+ mockFetch.mockResolvedValueOnce(jsonResponse({ events: [], total_event_names: 0 }));
204
+ await client.getTopEvents({ timeRange: "24h" });
205
+ const url = new URL(mockFetch.mock.calls[0][0]);
206
+ expect(url.pathname).toBe("/v1/query/top-events");
207
+ expect(url.searchParams.get("time_range")).toBe("24h");
208
+ });
209
+ it("includes optional limit", async () => {
210
+ const client = new ApiClient(makeConfig());
211
+ mockFetch.mockResolvedValueOnce(jsonResponse({ events: [], total_event_names: 0 }));
212
+ await client.getTopEvents({ timeRange: "7d", limit: 10 });
213
+ const url = new URL(mockFetch.mock.calls[0][0]);
214
+ expect(url.searchParams.get("limit")).toBe("10");
215
+ });
216
+ it("includes project_id from config default", async () => {
217
+ const client = new ApiClient(makeConfig({ projectId: "proj_top" }));
218
+ mockFetch.mockResolvedValueOnce(jsonResponse({ events: [], total_event_names: 0 }));
219
+ await client.getTopEvents({ timeRange: "7d" });
220
+ const url = new URL(mockFetch.mock.calls[0][0]);
221
+ expect(url.searchParams.get("project_id")).toBe("proj_top");
222
+ });
223
+ });
224
+ describe("getInsights", () => {
225
+ it("calls correct URL", async () => {
226
+ const client = new ApiClient(makeConfig());
227
+ mockFetch.mockResolvedValueOnce(jsonResponse({ insights: [], count: 0 }));
228
+ await client.getInsights();
229
+ const url = new URL(mockFetch.mock.calls[0][0]);
230
+ expect(url.pathname).toBe("/v1/query/insights");
231
+ });
232
+ it("includes optional severity filter", async () => {
233
+ const client = new ApiClient(makeConfig());
234
+ mockFetch.mockResolvedValueOnce(jsonResponse({ insights: [], count: 0 }));
235
+ await client.getInsights({ severity: "critical" });
236
+ const url = new URL(mockFetch.mock.calls[0][0]);
237
+ expect(url.searchParams.get("severity")).toBe("critical");
238
+ });
239
+ it("works without params", async () => {
240
+ const client = new ApiClient(makeConfig());
241
+ mockFetch.mockResolvedValueOnce(jsonResponse({ insights: [], count: 0 }));
242
+ await client.getInsights();
243
+ const url = new URL(mockFetch.mock.calls[0][0]);
244
+ // No query string when no params
245
+ expect(url.searchParams.has("severity")).toBe(false);
246
+ });
247
+ it("works with empty params object", async () => {
248
+ const client = new ApiClient(makeConfig());
249
+ mockFetch.mockResolvedValueOnce(jsonResponse({ insights: [], count: 0 }));
250
+ await client.getInsights({});
251
+ const url = new URL(mockFetch.mock.calls[0][0]);
252
+ expect(url.searchParams.has("severity")).toBe(false);
253
+ });
254
+ });
255
+ describe("error handling", () => {
256
+ it("throws ApiError with status 401 and auth failed message", async () => {
257
+ const client = new ApiClient(makeConfig());
258
+ mockFetch.mockResolvedValueOnce(textResponse("Unauthorized", 401));
259
+ try {
260
+ await client.getEventCounts({ eventName: "test", timeRange: "7d" });
261
+ expect.unreachable("Should have thrown");
262
+ }
263
+ catch (err) {
264
+ expect(err).toBeInstanceOf(ApiError);
265
+ expect(err.message).toMatch(/Authentication failed/);
266
+ }
267
+ });
268
+ it("includes statusCode 401 on ApiError", async () => {
269
+ const client = new ApiClient(makeConfig());
270
+ mockFetch.mockResolvedValueOnce(textResponse("Unauthorized", 401));
271
+ try {
272
+ await client.getEventCounts({ eventName: "test", timeRange: "7d" });
273
+ expect.unreachable("Should have thrown");
274
+ }
275
+ catch (err) {
276
+ expect(err).toBeInstanceOf(ApiError);
277
+ expect(err.statusCode).toBe(401);
278
+ }
279
+ });
280
+ it("throws ApiError with status 403 and access denied message", async () => {
281
+ const client = new ApiClient(makeConfig());
282
+ mockFetch.mockResolvedValueOnce(textResponse("Forbidden", 403));
283
+ await expect(client.getEventCounts({ eventName: "test", timeRange: "7d" })).rejects.toThrow(ApiError);
284
+ mockFetch.mockResolvedValueOnce(textResponse("Forbidden", 403));
285
+ await expect(client.getEventCounts({ eventName: "test", timeRange: "7d" })).rejects.toThrow(/Access denied/);
286
+ });
287
+ it("throws ApiError with status 500 and includes body detail", async () => {
288
+ const client = new ApiClient(makeConfig());
289
+ mockFetch.mockResolvedValueOnce(textResponse('{"error": "Internal server error"}', 500));
290
+ try {
291
+ await client.getEventCounts({ eventName: "test", timeRange: "7d" });
292
+ expect.unreachable("Should have thrown");
293
+ }
294
+ catch (err) {
295
+ expect(err).toBeInstanceOf(ApiError);
296
+ expect(err.statusCode).toBe(500);
297
+ expect(err.message).toContain("Internal server error");
298
+ }
299
+ });
300
+ it("throws ApiError on network failure with connection error", async () => {
301
+ const client = new ApiClient(makeConfig());
302
+ mockFetch.mockRejectedValueOnce(new Error("ECONNREFUSED"));
303
+ try {
304
+ await client.getEventCounts({ eventName: "test", timeRange: "7d" });
305
+ expect.unreachable("Should have thrown");
306
+ }
307
+ catch (err) {
308
+ expect(err).toBeInstanceOf(ApiError);
309
+ expect(err.statusCode).toBe(0);
310
+ expect(err.message).toContain("Failed to connect");
311
+ expect(err.message).toContain("ECONNREFUSED");
312
+ }
313
+ });
314
+ it("parses JSON error body when available", async () => {
315
+ const client = new ApiClient(makeConfig());
316
+ mockFetch.mockResolvedValueOnce({
317
+ ok: false,
318
+ status: 422,
319
+ text: () => Promise.resolve('{"error": "Invalid time range"}'),
320
+ });
321
+ try {
322
+ await client.getEventCounts({ eventName: "test", timeRange: "invalid" });
323
+ expect.unreachable("Should have thrown");
324
+ }
325
+ catch (err) {
326
+ expect(err).toBeInstanceOf(ApiError);
327
+ expect(err.message).toContain("Invalid time range");
328
+ }
329
+ });
330
+ it("uses message field from JSON error body", async () => {
331
+ const client = new ApiClient(makeConfig());
332
+ mockFetch.mockResolvedValueOnce({
333
+ ok: false,
334
+ status: 400,
335
+ text: () => Promise.resolve('{"message": "Bad request format"}'),
336
+ });
337
+ try {
338
+ await client.getEventCounts({ eventName: "test", timeRange: "7d" });
339
+ expect.unreachable("Should have thrown");
340
+ }
341
+ catch (err) {
342
+ expect(err).toBeInstanceOf(ApiError);
343
+ expect(err.message).toContain("Bad request format");
344
+ }
345
+ });
346
+ it("falls back to raw text when body is not JSON", async () => {
347
+ const client = new ApiClient(makeConfig());
348
+ mockFetch.mockResolvedValueOnce({
349
+ ok: false,
350
+ status: 502,
351
+ text: () => Promise.resolve("Bad Gateway"),
352
+ });
353
+ try {
354
+ await client.getEventCounts({ eventName: "test", timeRange: "7d" });
355
+ expect.unreachable("Should have thrown");
356
+ }
357
+ catch (err) {
358
+ expect(err).toBeInstanceOf(ApiError);
359
+ expect(err.message).toContain("Bad Gateway");
360
+ expect(err.statusCode).toBe(502);
361
+ }
362
+ });
363
+ });
364
+ describe("ApiError", () => {
365
+ it('has name "ApiError"', () => {
366
+ const err = new ApiError("test", 500);
367
+ expect(err.name).toBe("ApiError");
368
+ });
369
+ it("extends Error", () => {
370
+ const err = new ApiError("test", 500);
371
+ expect(err).toBeInstanceOf(Error);
372
+ });
373
+ it("stores statusCode", () => {
374
+ const err = new ApiError("test", 404);
375
+ expect(err.statusCode).toBe(404);
376
+ });
377
+ it("stores message", () => {
378
+ const err = new ApiError("something went wrong", 500);
379
+ expect(err.message).toBe("something went wrong");
380
+ });
381
+ });
382
+ });
383
+ //# sourceMappingURL=api-client.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.test.js","sourceRoot":"","sources":["../src/api-client.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=charts.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"charts.test.d.ts","sourceRoot":"","sources":["../src/charts.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,218 @@
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"charts.test.js","sourceRoot":"","sources":["../src/charts.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,409 @@
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tips.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tips.test.d.ts","sourceRoot":"","sources":["../src/tips.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,92 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ // Tips module uses module-level Map state, so we need fresh imports for isolation.
3
+ // vi.resetModules() + dynamic import gives us a clean module each time.
4
+ async function freshGetTip() {
5
+ vi.resetModules();
6
+ const mod = await import("./tips.js");
7
+ return mod.getTip;
8
+ }
9
+ describe("getTip", () => {
10
+ beforeEach(() => {
11
+ vi.resetModules();
12
+ });
13
+ it("returns a tip string for a valid category", async () => {
14
+ const getTip = await freshGetTip();
15
+ const tip = getTip("afterCounts");
16
+ expect(tip).toBeTruthy();
17
+ expect(typeof tip).toBe("string");
18
+ expect(tip.length).toBeGreaterThan(0);
19
+ });
20
+ it("returns tip prefixed with double newline", async () => {
21
+ const getTip = await freshGetTip();
22
+ const tip = getTip("afterCounts");
23
+ expect(tip).toMatch(/^\n\n/);
24
+ });
25
+ it("returns different tips on subsequent calls (rotation)", async () => {
26
+ const getTip = await freshGetTip();
27
+ const tip1 = getTip("afterCounts");
28
+ const tip2 = getTip("afterCounts");
29
+ const tip3 = getTip("afterCounts");
30
+ expect(tip1).not.toBe(tip2);
31
+ expect(tip2).not.toBe(tip3);
32
+ });
33
+ it("returns tips in order (index 0, then 1, then 2...)", async () => {
34
+ const getTip = await freshGetTip();
35
+ const tip1 = getTip("afterRetention");
36
+ // The first tip should contain the first item's content
37
+ expect(tip1).toContain("Track this over time");
38
+ });
39
+ it("resets and starts over after all tips are shown", async () => {
40
+ const getTip = await freshGetTip();
41
+ // afterTopEvents has 4 tips
42
+ const firstRound = [];
43
+ for (let i = 0; i < 4; i++) {
44
+ firstRound.push(getTip("afterTopEvents"));
45
+ }
46
+ // All 4 should be unique
47
+ const unique = new Set(firstRound);
48
+ expect(unique.size).toBe(4);
49
+ // Next call should reset and return the first tip again
50
+ const afterReset = getTip("afterTopEvents");
51
+ expect(afterReset).toBe(firstRound[0]);
52
+ });
53
+ it("tracks categories independently", async () => {
54
+ const getTip = await freshGetTip();
55
+ const countsTip = getTip("afterCounts");
56
+ const retentionTip = getTip("afterRetention");
57
+ // Both should return their first tips, not share state
58
+ expect(countsTip).toContain("automatically");
59
+ expect(retentionTip).toContain("Track this over time");
60
+ });
61
+ it("works for afterCounts category", async () => {
62
+ const getTip = await freshGetTip();
63
+ const tip = getTip("afterCounts");
64
+ expect(tip).toBeTruthy();
65
+ expect(tip).toContain("💡");
66
+ });
67
+ it("works for afterRetention category", async () => {
68
+ const getTip = await freshGetTip();
69
+ const tip = getTip("afterRetention");
70
+ expect(tip).toBeTruthy();
71
+ expect(tip).toContain("💡");
72
+ });
73
+ it("works for afterJourney category", async () => {
74
+ const getTip = await freshGetTip();
75
+ const tip = getTip("afterJourney");
76
+ expect(tip).toBeTruthy();
77
+ expect(tip).toContain("💡");
78
+ });
79
+ it("works for afterTopEvents category", async () => {
80
+ const getTip = await freshGetTip();
81
+ const tip = getTip("afterTopEvents");
82
+ expect(tip).toBeTruthy();
83
+ expect(tip).toContain("💡");
84
+ });
85
+ it("works for afterInsights category", async () => {
86
+ const getTip = await freshGetTip();
87
+ const tip = getTip("afterInsights");
88
+ expect(tip).toBeTruthy();
89
+ expect(tip).toContain("💡");
90
+ });
91
+ });
92
+ //# sourceMappingURL=tips.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tips.test.js","sourceRoot":"","sources":["../src/tips.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inferevents/mcp",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "MCP server for Infer analytics — 5 tools including auto-detected insights pushed hourly",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",