mongodb-mcp-server 0.0.8 → 0.1.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.
Files changed (90) hide show
  1. package/.github/CODEOWNERS +0 -2
  2. package/.github/ISSUE_TEMPLATE/bug_report.yml +8 -0
  3. package/.github/workflows/{lint.yml → check.yml} +22 -1
  4. package/.github/workflows/code_health.yaml +0 -22
  5. package/.github/workflows/code_health_fork.yaml +7 -63
  6. package/.vscode/extensions.json +9 -0
  7. package/.vscode/settings.json +11 -0
  8. package/README.md +41 -22
  9. package/dist/common/atlas/apiClient.js +141 -34
  10. package/dist/common/atlas/apiClient.js.map +1 -1
  11. package/dist/common/atlas/apiClientError.js +38 -5
  12. package/dist/common/atlas/apiClientError.js.map +1 -1
  13. package/dist/common/atlas/cluster.js +66 -0
  14. package/dist/common/atlas/cluster.js.map +1 -0
  15. package/dist/common/atlas/generatePassword.js +9 -0
  16. package/dist/common/atlas/generatePassword.js.map +1 -0
  17. package/dist/helpers/EJsonTransport.js +38 -0
  18. package/dist/helpers/EJsonTransport.js.map +1 -0
  19. package/dist/helpers/connectionOptions.js +10 -0
  20. package/dist/helpers/connectionOptions.js.map +1 -0
  21. package/dist/{packageInfo.js → helpers/packageInfo.js} +1 -1
  22. package/dist/helpers/packageInfo.js.map +1 -0
  23. package/dist/index.js +6 -3
  24. package/dist/index.js.map +1 -1
  25. package/dist/logger.js +2 -0
  26. package/dist/logger.js.map +1 -1
  27. package/dist/server.js +15 -11
  28. package/dist/server.js.map +1 -1
  29. package/dist/session.js +8 -3
  30. package/dist/session.js.map +1 -1
  31. package/dist/telemetry/constants.js +1 -3
  32. package/dist/telemetry/constants.js.map +1 -1
  33. package/dist/telemetry/eventCache.js.map +1 -1
  34. package/dist/telemetry/telemetry.js +46 -4
  35. package/dist/telemetry/telemetry.js.map +1 -1
  36. package/dist/tools/atlas/atlasTool.js +38 -0
  37. package/dist/tools/atlas/atlasTool.js.map +1 -1
  38. package/dist/tools/atlas/create/createDBUser.js +19 -2
  39. package/dist/tools/atlas/create/createDBUser.js.map +1 -1
  40. package/dist/tools/atlas/metadata/connectCluster.js +5 -22
  41. package/dist/tools/atlas/metadata/connectCluster.js.map +1 -1
  42. package/dist/tools/atlas/read/inspectCluster.js +4 -24
  43. package/dist/tools/atlas/read/inspectCluster.js.map +1 -1
  44. package/dist/tools/atlas/read/listClusters.js +9 -18
  45. package/dist/tools/atlas/read/listClusters.js.map +1 -1
  46. package/dist/tools/mongodb/tools.js +2 -4
  47. package/dist/tools/mongodb/tools.js.map +1 -1
  48. package/eslint.config.js +2 -1
  49. package/{jest.config.ts → jest.config.cjs} +1 -1
  50. package/package.json +4 -2
  51. package/scripts/apply.ts +4 -1
  52. package/scripts/filter.ts +4 -0
  53. package/src/common/atlas/apiClient.ts +179 -37
  54. package/src/common/atlas/apiClientError.ts +58 -7
  55. package/src/common/atlas/cluster.ts +95 -0
  56. package/src/common/atlas/generatePassword.ts +10 -0
  57. package/src/common/atlas/openapi.d.ts +438 -15
  58. package/src/helpers/EJsonTransport.ts +47 -0
  59. package/src/helpers/connectionOptions.ts +20 -0
  60. package/src/{packageInfo.ts → helpers/packageInfo.ts} +1 -1
  61. package/src/index.ts +7 -3
  62. package/src/logger.ts +2 -0
  63. package/src/server.ts +22 -14
  64. package/src/session.ts +8 -4
  65. package/src/telemetry/constants.ts +2 -3
  66. package/src/telemetry/eventCache.ts +1 -1
  67. package/src/telemetry/telemetry.ts +72 -6
  68. package/src/telemetry/types.ts +0 -1
  69. package/src/tools/atlas/atlasTool.ts +47 -1
  70. package/src/tools/atlas/create/createDBUser.ts +22 -2
  71. package/src/tools/atlas/metadata/connectCluster.ts +5 -27
  72. package/src/tools/atlas/read/inspectCluster.ts +4 -40
  73. package/src/tools/atlas/read/listClusters.ts +19 -36
  74. package/src/tools/mongodb/tools.ts +2 -4
  75. package/src/types/mongodb-connection-string-url.d.ts +69 -0
  76. package/tests/integration/helpers.ts +18 -2
  77. package/tests/integration/telemetry.test.ts +28 -0
  78. package/tests/integration/tools/atlas/dbUsers.test.ts +57 -32
  79. package/tests/integration/tools/mongodb/metadata/connect.test.ts +2 -6
  80. package/tests/integration/tools/mongodb/mongodbHelpers.ts +15 -24
  81. package/tests/integration/tools/mongodb/read/find.test.ts +28 -0
  82. package/tests/unit/EJsonTransport.test.ts +71 -0
  83. package/tests/unit/apiClient.test.ts +193 -0
  84. package/tests/unit/session.test.ts +65 -0
  85. package/tests/unit/telemetry.test.ts +165 -71
  86. package/tsconfig.build.json +1 -1
  87. package/dist/packageInfo.js.map +0 -1
  88. package/dist/telemetry/device-id.js +0 -20
  89. package/dist/telemetry/device-id.js.map +0 -1
  90. package/src/telemetry/device-id.ts +0 -21
@@ -0,0 +1,193 @@
1
+ import { jest } from "@jest/globals";
2
+ import { ApiClient } from "../../src/common/atlas/apiClient.js";
3
+ import { CommonProperties, TelemetryEvent, TelemetryResult } from "../../src/telemetry/types.js";
4
+
5
+ describe("ApiClient", () => {
6
+ let apiClient: ApiClient;
7
+
8
+ const mockEvents: TelemetryEvent<CommonProperties>[] = [
9
+ {
10
+ timestamp: new Date().toISOString(),
11
+ source: "mdbmcp",
12
+ properties: {
13
+ mcp_client_version: "1.0.0",
14
+ mcp_client_name: "test-client",
15
+ mcp_server_version: "1.0.0",
16
+ mcp_server_name: "test-server",
17
+ platform: "test-platform",
18
+ arch: "test-arch",
19
+ os_type: "test-os",
20
+ component: "test-component",
21
+ duration_ms: 100,
22
+ result: "success" as TelemetryResult,
23
+ category: "test-category",
24
+ },
25
+ },
26
+ ];
27
+
28
+ beforeEach(() => {
29
+ apiClient = new ApiClient({
30
+ baseUrl: "https://api.test.com",
31
+ credentials: {
32
+ clientId: "test-client-id",
33
+ clientSecret: "test-client-secret",
34
+ },
35
+ userAgent: "test-user-agent",
36
+ });
37
+
38
+ // @ts-expect-error accessing private property for testing
39
+ apiClient.getAccessToken = jest.fn().mockResolvedValue("mockToken");
40
+ });
41
+
42
+ afterEach(() => {
43
+ jest.clearAllMocks();
44
+ });
45
+
46
+ describe("constructor", () => {
47
+ it("should create a client with the correct configuration", () => {
48
+ expect(apiClient).toBeDefined();
49
+ expect(apiClient.hasCredentials()).toBeDefined();
50
+ });
51
+ });
52
+
53
+ describe("listProjects", () => {
54
+ it("should return a list of projects", async () => {
55
+ const mockProjects = {
56
+ results: [
57
+ { id: "1", name: "Project 1" },
58
+ { id: "2", name: "Project 2" },
59
+ ],
60
+ totalCount: 2,
61
+ };
62
+
63
+ const mockGet = jest.fn().mockImplementation(() => ({
64
+ data: mockProjects,
65
+ error: null,
66
+ response: new Response(),
67
+ }));
68
+
69
+ // @ts-expect-error accessing private property for testing
70
+ apiClient.client.GET = mockGet;
71
+
72
+ const result = await apiClient.listProjects();
73
+
74
+ expect(mockGet).toHaveBeenCalledWith("/api/atlas/v2/groups", undefined);
75
+ expect(result).toEqual(mockProjects);
76
+ });
77
+
78
+ it("should throw an error when the API call fails", async () => {
79
+ const mockError = {
80
+ reason: "Test error",
81
+ detail: "Something went wrong",
82
+ };
83
+
84
+ const mockGet = jest.fn().mockImplementation(() => ({
85
+ data: null,
86
+ error: mockError,
87
+ response: new Response(),
88
+ }));
89
+
90
+ // @ts-expect-error accessing private property for testing
91
+ apiClient.client.GET = mockGet;
92
+
93
+ await expect(apiClient.listProjects()).rejects.toThrow();
94
+ });
95
+ });
96
+
97
+ describe("sendEvents", () => {
98
+ it("should send events to authenticated endpoint when token is available and valid", async () => {
99
+ const mockFetch = jest.spyOn(global, "fetch");
100
+ mockFetch.mockResolvedValueOnce(new Response(null, { status: 200 }));
101
+
102
+ await apiClient.sendEvents(mockEvents);
103
+
104
+ const url = new URL("api/private/v1.0/telemetry/events", "https://api.test.com");
105
+ expect(mockFetch).toHaveBeenCalledWith(url, {
106
+ method: "POST",
107
+ headers: {
108
+ "Content-Type": "application/json",
109
+ Authorization: "Bearer mockToken",
110
+ Accept: "application/json",
111
+ "User-Agent": "test-user-agent",
112
+ },
113
+ body: JSON.stringify(mockEvents),
114
+ });
115
+ });
116
+
117
+ it("should fall back to unauthenticated endpoint when token is not available via exception", async () => {
118
+ const mockFetch = jest.spyOn(global, "fetch");
119
+ mockFetch.mockResolvedValueOnce(new Response(null, { status: 200 }));
120
+
121
+ // @ts-expect-error accessing private property for testing
122
+ apiClient.getAccessToken = jest.fn().mockRejectedValue(new Error("No access token available"));
123
+
124
+ await apiClient.sendEvents(mockEvents);
125
+
126
+ const url = new URL("api/private/unauth/telemetry/events", "https://api.test.com");
127
+ expect(mockFetch).toHaveBeenCalledWith(url, {
128
+ method: "POST",
129
+ headers: {
130
+ "Content-Type": "application/json",
131
+ Accept: "application/json",
132
+ "User-Agent": "test-user-agent",
133
+ },
134
+ body: JSON.stringify(mockEvents),
135
+ });
136
+ });
137
+
138
+ it("should fall back to unauthenticated endpoint when token is undefined", async () => {
139
+ const mockFetch = jest.spyOn(global, "fetch");
140
+ mockFetch.mockResolvedValueOnce(new Response(null, { status: 200 }));
141
+
142
+ // @ts-expect-error accessing private property for testing
143
+ apiClient.getAccessToken = jest.fn().mockReturnValueOnce(undefined);
144
+
145
+ await apiClient.sendEvents(mockEvents);
146
+
147
+ const url = new URL("api/private/unauth/telemetry/events", "https://api.test.com");
148
+ expect(mockFetch).toHaveBeenCalledWith(url, {
149
+ method: "POST",
150
+ headers: {
151
+ "Content-Type": "application/json",
152
+ Accept: "application/json",
153
+ "User-Agent": "test-user-agent",
154
+ },
155
+ body: JSON.stringify(mockEvents),
156
+ });
157
+ });
158
+
159
+ it("should fall back to unauthenticated endpoint on 401 error", async () => {
160
+ const mockFetch = jest.spyOn(global, "fetch");
161
+ mockFetch
162
+ .mockResolvedValueOnce(new Response(null, { status: 401 }))
163
+ .mockResolvedValueOnce(new Response(null, { status: 200 }));
164
+
165
+ await apiClient.sendEvents(mockEvents);
166
+
167
+ const url = new URL("api/private/unauth/telemetry/events", "https://api.test.com");
168
+ expect(mockFetch).toHaveBeenCalledTimes(2);
169
+ expect(mockFetch).toHaveBeenLastCalledWith(url, {
170
+ method: "POST",
171
+ headers: {
172
+ "Content-Type": "application/json",
173
+ Accept: "application/json",
174
+ "User-Agent": "test-user-agent",
175
+ },
176
+ body: JSON.stringify(mockEvents),
177
+ });
178
+ });
179
+
180
+ it("should throw error when both authenticated and unauthenticated requests fail", async () => {
181
+ const mockFetch = jest.spyOn(global, "fetch");
182
+ mockFetch
183
+ .mockResolvedValueOnce(new Response(null, { status: 401 }))
184
+ .mockResolvedValueOnce(new Response(null, { status: 500 }));
185
+
186
+ const mockToken = "test-token";
187
+ // @ts-expect-error accessing private property for testing
188
+ apiClient.getAccessToken = jest.fn().mockResolvedValue(mockToken);
189
+
190
+ await expect(apiClient.sendEvents(mockEvents)).rejects.toThrow();
191
+ });
192
+ });
193
+ });
@@ -0,0 +1,65 @@
1
+ import { jest } from "@jest/globals";
2
+ import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
3
+ import { Session } from "../../src/session.js";
4
+ import { config } from "../../src/config.js";
5
+
6
+ jest.mock("@mongosh/service-provider-node-driver");
7
+ const MockNodeDriverServiceProvider = NodeDriverServiceProvider as jest.MockedClass<typeof NodeDriverServiceProvider>;
8
+
9
+ describe("Session", () => {
10
+ let session: Session;
11
+ beforeEach(() => {
12
+ session = new Session({
13
+ apiClientId: "test-client-id",
14
+ apiBaseUrl: "https://api.test.com",
15
+ });
16
+
17
+ MockNodeDriverServiceProvider.connect = jest.fn(() =>
18
+ Promise.resolve({} as unknown as NodeDriverServiceProvider)
19
+ );
20
+ });
21
+
22
+ describe("connectToMongoDB", () => {
23
+ const testCases: {
24
+ connectionString: string;
25
+ expectAppName: boolean;
26
+ name: string;
27
+ }[] = [
28
+ {
29
+ connectionString: "mongodb://localhost:27017",
30
+ expectAppName: true,
31
+ name: "db without appName",
32
+ },
33
+ {
34
+ connectionString: "mongodb://localhost:27017?appName=CustomAppName",
35
+ expectAppName: false,
36
+ name: "db with custom appName",
37
+ },
38
+ {
39
+ connectionString:
40
+ "mongodb+srv://test.mongodb.net/test?retryWrites=true&w=majority&appName=CustomAppName",
41
+ expectAppName: false,
42
+ name: "atlas db with custom appName",
43
+ },
44
+ ];
45
+
46
+ for (const testCase of testCases) {
47
+ it(`should update connection string for ${testCase.name}`, async () => {
48
+ await session.connectToMongoDB(testCase.connectionString, config.connectOptions);
49
+ expect(session.serviceProvider).toBeDefined();
50
+
51
+ // eslint-disable-next-line @typescript-eslint/unbound-method
52
+ const connectMock = MockNodeDriverServiceProvider.connect as jest.Mock<
53
+ typeof NodeDriverServiceProvider.connect
54
+ >;
55
+ expect(connectMock).toHaveBeenCalledOnce();
56
+ const connectionString = connectMock.mock.calls[0][0];
57
+ if (testCase.expectAppName) {
58
+ expect(connectionString).toContain("appName=MongoDB+MCP+Server");
59
+ } else {
60
+ expect(connectionString).not.toContain("appName=MongoDB+MCP+Server");
61
+ }
62
+ });
63
+ }
64
+ });
65
+ });
@@ -1,10 +1,12 @@
1
1
  import { ApiClient } from "../../src/common/atlas/apiClient.js";
2
2
  import { Session } from "../../src/session.js";
3
- import { Telemetry } from "../../src/telemetry/telemetry.js";
3
+ import { DEVICE_ID_TIMEOUT, Telemetry } from "../../src/telemetry/telemetry.js";
4
4
  import { BaseEvent, TelemetryResult } from "../../src/telemetry/types.js";
5
5
  import { EventCache } from "../../src/telemetry/eventCache.js";
6
6
  import { config } from "../../src/config.js";
7
7
  import { jest } from "@jest/globals";
8
+ import logger, { LogId } from "../../src/logger.js";
9
+ import { createHmac } from "crypto";
8
10
 
9
11
  // Mock the ApiClient to avoid real API calls
10
12
  jest.mock("../../src/common/atlas/apiClient.js");
@@ -15,6 +17,9 @@ jest.mock("../../src/telemetry/eventCache.js");
15
17
  const MockEventCache = EventCache as jest.MockedClass<typeof EventCache>;
16
18
 
17
19
  describe("Telemetry", () => {
20
+ const machineId = "test-machine-id";
21
+ const hashedMachineId = createHmac("sha256", machineId.toUpperCase()).update("atlascli").digest("hex");
22
+
18
23
  let mockApiClient: jest.Mocked<ApiClient>;
19
24
  let mockEventCache: jest.Mocked<EventCache>;
20
25
  let session: Session;
@@ -120,109 +125,198 @@ describe("Telemetry", () => {
120
125
  setAgentRunner: jest.fn().mockResolvedValue(undefined),
121
126
  } as unknown as Session;
122
127
 
123
- // Create the telemetry instance with mocked dependencies
124
- telemetry = new Telemetry(session, config, mockEventCache);
128
+ telemetry = Telemetry.create(session, config, {
129
+ eventCache: mockEventCache,
130
+ getRawMachineId: () => Promise.resolve(machineId),
131
+ });
132
+
125
133
  config.telemetry = "enabled";
126
134
  });
127
135
 
128
- describe("when telemetry is enabled", () => {
129
- it("should send events successfully", async () => {
130
- const testEvent = createTestEvent();
136
+ describe("sending events", () => {
137
+ describe("when telemetry is enabled", () => {
138
+ it("should send events successfully", async () => {
139
+ const testEvent = createTestEvent();
131
140
 
132
- await telemetry.emitEvents([testEvent]);
141
+ await telemetry.emitEvents([testEvent]);
133
142
 
134
- verifyMockCalls({
135
- sendEventsCalls: 1,
136
- clearEventsCalls: 1,
137
- sendEventsCalledWith: [testEvent],
143
+ verifyMockCalls({
144
+ sendEventsCalls: 1,
145
+ clearEventsCalls: 1,
146
+ sendEventsCalledWith: [testEvent],
147
+ });
138
148
  });
139
- });
140
149
 
141
- it("should cache events when sending fails", async () => {
142
- mockApiClient.sendEvents.mockRejectedValueOnce(new Error("API error"));
150
+ it("should cache events when sending fails", async () => {
151
+ mockApiClient.sendEvents.mockRejectedValueOnce(new Error("API error"));
143
152
 
144
- const testEvent = createTestEvent();
153
+ const testEvent = createTestEvent();
145
154
 
146
- await telemetry.emitEvents([testEvent]);
155
+ await telemetry.emitEvents([testEvent]);
147
156
 
148
- verifyMockCalls({
149
- sendEventsCalls: 1,
150
- appendEventsCalls: 1,
151
- appendEventsCalledWith: [testEvent],
157
+ verifyMockCalls({
158
+ sendEventsCalls: 1,
159
+ appendEventsCalls: 1,
160
+ appendEventsCalledWith: [testEvent],
161
+ });
152
162
  });
153
- });
154
163
 
155
- it("should include cached events when sending", async () => {
156
- const cachedEvent = createTestEvent({
157
- command: "cached-command",
158
- component: "cached-component",
159
- });
164
+ it("should include cached events when sending", async () => {
165
+ const cachedEvent = createTestEvent({
166
+ command: "cached-command",
167
+ component: "cached-component",
168
+ });
160
169
 
161
- const newEvent = createTestEvent({
162
- command: "new-command",
163
- component: "new-component",
170
+ const newEvent = createTestEvent({
171
+ command: "new-command",
172
+ component: "new-component",
173
+ });
174
+
175
+ // Set up mock to return cached events
176
+ mockEventCache.getEvents.mockReturnValueOnce([cachedEvent]);
177
+
178
+ await telemetry.emitEvents([newEvent]);
179
+
180
+ verifyMockCalls({
181
+ sendEventsCalls: 1,
182
+ clearEventsCalls: 1,
183
+ sendEventsCalledWith: [cachedEvent, newEvent],
184
+ });
164
185
  });
165
186
 
166
- // Set up mock to return cached events
167
- mockEventCache.getEvents.mockReturnValueOnce([cachedEvent]);
187
+ it("should correctly add common properties to events", () => {
188
+ const commonProps = telemetry.getCommonProperties();
168
189
 
169
- await telemetry.emitEvents([newEvent]);
190
+ // Use explicit type assertion
191
+ const expectedProps: Record<string, string> = {
192
+ mcp_client_version: "1.0.0",
193
+ mcp_client_name: "test-agent",
194
+ session_id: "test-session-id",
195
+ config_atlas_auth: "true",
196
+ config_connection_string: expect.any(String) as unknown as string,
197
+ device_id: hashedMachineId,
198
+ };
170
199
 
171
- verifyMockCalls({
172
- sendEventsCalls: 1,
173
- clearEventsCalls: 1,
174
- sendEventsCalledWith: [cachedEvent, newEvent],
200
+ expect(commonProps).toMatchObject(expectedProps);
175
201
  });
176
- });
177
- });
178
202
 
179
- describe("when telemetry is disabled", () => {
180
- beforeEach(() => {
181
- config.telemetry = "disabled";
182
- });
203
+ describe("machine ID resolution", () => {
204
+ beforeEach(() => {
205
+ jest.clearAllMocks();
206
+ jest.useFakeTimers();
207
+ });
183
208
 
184
- it("should not send events", async () => {
185
- const testEvent = createTestEvent();
209
+ afterEach(() => {
210
+ jest.clearAllMocks();
211
+ jest.useRealTimers();
212
+ });
186
213
 
187
- await telemetry.emitEvents([testEvent]);
214
+ it("should successfully resolve the machine ID", async () => {
215
+ telemetry = Telemetry.create(session, config, {
216
+ getRawMachineId: () => Promise.resolve(machineId),
217
+ });
188
218
 
189
- verifyMockCalls();
190
- });
191
- });
219
+ expect(telemetry["isBufferingEvents"]).toBe(true);
220
+ expect(telemetry.getCommonProperties().device_id).toBe(undefined);
192
221
 
193
- it("should correctly add common properties to events", () => {
194
- const commonProps = telemetry.getCommonProperties();
222
+ await telemetry.deviceIdPromise;
195
223
 
196
- // Use explicit type assertion
197
- const expectedProps: Record<string, string> = {
198
- mcp_client_version: "1.0.0",
199
- mcp_client_name: "test-agent",
200
- session_id: "test-session-id",
201
- config_atlas_auth: "true",
202
- config_connection_string: expect.any(String) as unknown as string,
203
- };
224
+ expect(telemetry["isBufferingEvents"]).toBe(false);
225
+ expect(telemetry.getCommonProperties().device_id).toBe(hashedMachineId);
226
+ });
204
227
 
205
- expect(commonProps).toMatchObject(expectedProps);
206
- });
228
+ it("should handle machine ID resolution failure", async () => {
229
+ const loggerSpy = jest.spyOn(logger, "debug");
230
+
231
+ telemetry = Telemetry.create(session, config, {
232
+ getRawMachineId: () => Promise.reject(new Error("Failed to get device ID")),
233
+ });
234
+
235
+ expect(telemetry["isBufferingEvents"]).toBe(true);
236
+ expect(telemetry.getCommonProperties().device_id).toBe(undefined);
237
+
238
+ await telemetry.deviceIdPromise;
239
+
240
+ expect(telemetry["isBufferingEvents"]).toBe(false);
241
+ expect(telemetry.getCommonProperties().device_id).toBe("unknown");
242
+
243
+ expect(loggerSpy).toHaveBeenCalledWith(
244
+ LogId.telemetryDeviceIdFailure,
245
+ "telemetry",
246
+ "Error: Failed to get device ID"
247
+ );
248
+ });
207
249
 
208
- describe("when DO_NOT_TRACK environment variable is set", () => {
209
- let originalEnv: string | undefined;
250
+ it("should timeout if machine ID resolution takes too long", async () => {
251
+ const loggerSpy = jest.spyOn(logger, "debug");
210
252
 
211
- beforeEach(() => {
212
- originalEnv = process.env.DO_NOT_TRACK;
213
- process.env.DO_NOT_TRACK = "1";
253
+ telemetry = Telemetry.create(session, config, { getRawMachineId: () => new Promise(() => {}) });
254
+
255
+ expect(telemetry["isBufferingEvents"]).toBe(true);
256
+ expect(telemetry.getCommonProperties().device_id).toBe(undefined);
257
+
258
+ jest.advanceTimersByTime(DEVICE_ID_TIMEOUT / 2);
259
+
260
+ // Make sure the timeout doesn't happen prematurely.
261
+ expect(telemetry["isBufferingEvents"]).toBe(true);
262
+ expect(telemetry.getCommonProperties().device_id).toBe(undefined);
263
+
264
+ jest.advanceTimersByTime(DEVICE_ID_TIMEOUT);
265
+
266
+ await telemetry.deviceIdPromise;
267
+
268
+ expect(telemetry.getCommonProperties().device_id).toBe("unknown");
269
+ expect(telemetry["isBufferingEvents"]).toBe(false);
270
+ expect(loggerSpy).toHaveBeenCalledWith(
271
+ LogId.telemetryDeviceIdTimeout,
272
+ "telemetry",
273
+ "Device ID retrieval timed out"
274
+ );
275
+ });
276
+ });
214
277
  });
215
278
 
216
- afterEach(() => {
217
- process.env.DO_NOT_TRACK = originalEnv;
279
+ describe("when telemetry is disabled", () => {
280
+ beforeEach(() => {
281
+ config.telemetry = "disabled";
282
+ });
283
+
284
+ afterEach(() => {
285
+ config.telemetry = "enabled";
286
+ });
287
+
288
+ it("should not send events", async () => {
289
+ const testEvent = createTestEvent();
290
+
291
+ await telemetry.emitEvents([testEvent]);
292
+
293
+ verifyMockCalls();
294
+ });
218
295
  });
219
296
 
220
- it("should not send events", async () => {
221
- const testEvent = createTestEvent();
297
+ describe("when DO_NOT_TRACK environment variable is set", () => {
298
+ let originalEnv: string | undefined;
299
+
300
+ beforeEach(() => {
301
+ originalEnv = process.env.DO_NOT_TRACK;
302
+ process.env.DO_NOT_TRACK = "1";
303
+ });
304
+
305
+ afterEach(() => {
306
+ if (originalEnv) {
307
+ process.env.DO_NOT_TRACK = originalEnv;
308
+ } else {
309
+ delete process.env.DO_NOT_TRACK;
310
+ }
311
+ });
222
312
 
223
- await telemetry.emitEvents([testEvent]);
313
+ it("should not send events", async () => {
314
+ const testEvent = createTestEvent();
224
315
 
225
- verifyMockCalls();
316
+ await telemetry.emitEvents([testEvent]);
317
+
318
+ verifyMockCalls();
319
+ });
226
320
  });
227
321
  });
228
322
  });
@@ -8,7 +8,7 @@
8
8
  "strict": true,
9
9
  "strictNullChecks": true,
10
10
  "esModuleInterop": true,
11
- "types": ["node", "jest"],
11
+ "types": ["node"],
12
12
  "sourceMap": true,
13
13
  "skipLibCheck": true,
14
14
  "resolveJsonModule": true,
@@ -1 +0,0 @@
1
- {"version":3,"file":"packageInfo.js","sourceRoot":"","sources":["../src/packageInfo.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAEhE,MAAM,CAAC,MAAM,WAAW,GAAG;IACvB,OAAO,EAAE,WAAW,CAAC,OAAO;IAC5B,aAAa,EAAE,oBAAoB;CACtC,CAAC"}
@@ -1,20 +0,0 @@
1
- import { createHmac } from "crypto";
2
- import nodeMachineId from "node-machine-id";
3
- import logger, { LogId } from "../logger.js";
4
- export function getDeviceId() {
5
- try {
6
- const originalId = nodeMachineId.machineIdSync(true);
7
- // Create a hashed format from the all uppercase version of the machine ID
8
- // to match it exactly with the denisbrodbeck/machineid library that Atlas CLI uses.
9
- const hmac = createHmac("sha256", originalId.toUpperCase());
10
- /** This matches the message used to create the hashes in Atlas CLI */
11
- const DEVICE_ID_HASH_MESSAGE = "atlascli";
12
- hmac.update(DEVICE_ID_HASH_MESSAGE);
13
- return hmac.digest("hex");
14
- }
15
- catch (error) {
16
- logger.debug(LogId.telemetryDeviceIdFailure, "telemetry", String(error));
17
- return "unknown";
18
- }
19
- }
20
- //# sourceMappingURL=device-id.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"device-id.js","sourceRoot":"","sources":["../../src/telemetry/device-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,aAAa,MAAM,iBAAiB,CAAC;AAC5C,OAAO,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,UAAU,WAAW;IACvB,IAAI,CAAC;QACD,MAAM,UAAU,GAAG,aAAa,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QACrD,0EAA0E;QAC1E,oFAAoF;QACpF,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC;QAE5D,sEAAsE;QACtE,MAAM,sBAAsB,GAAG,UAAU,CAAC;QAE1C,IAAI,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;QACpC,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,wBAAwB,EAAE,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACzE,OAAO,SAAS,CAAC;IACrB,CAAC;AACL,CAAC"}
@@ -1,21 +0,0 @@
1
- import { createHmac } from "crypto";
2
- import nodeMachineId from "node-machine-id";
3
- import logger, { LogId } from "../logger.js";
4
-
5
- export function getDeviceId(): string {
6
- try {
7
- const originalId = nodeMachineId.machineIdSync(true);
8
- // Create a hashed format from the all uppercase version of the machine ID
9
- // to match it exactly with the denisbrodbeck/machineid library that Atlas CLI uses.
10
- const hmac = createHmac("sha256", originalId.toUpperCase());
11
-
12
- /** This matches the message used to create the hashes in Atlas CLI */
13
- const DEVICE_ID_HASH_MESSAGE = "atlascli";
14
-
15
- hmac.update(DEVICE_ID_HASH_MESSAGE);
16
- return hmac.digest("hex");
17
- } catch (error) {
18
- logger.debug(LogId.telemetryDeviceIdFailure, "telemetry", String(error));
19
- return "unknown";
20
- }
21
- }