@positronic/cli 0.0.3 → 0.0.4
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/dist/src/commands/helpers.js +11 -25
- package/dist/types/commands/helpers.d.ts.map +1 -1
- package/package.json +5 -1
- package/dist/src/commands/brain.test.js +0 -2936
- package/dist/src/commands/helpers.test.js +0 -832
- package/dist/src/commands/project.test.js +0 -1201
- package/dist/src/commands/resources.test.js +0 -2511
- package/dist/src/commands/schedule.test.js +0 -1235
- package/dist/src/commands/secret.test.d.js +0 -1
- package/dist/src/commands/secret.test.js +0 -761
- package/dist/src/commands/server.test.js +0 -1237
- package/dist/src/commands/test-utils.js +0 -737
- package/dist/src/components/secret-sync.js +0 -303
- package/dist/src/test/mock-api-client.js +0 -371
- package/dist/src/test/test-dev-server.js +0 -1376
- package/dist/types/commands/test-utils.d.ts +0 -45
- package/dist/types/commands/test-utils.d.ts.map +0 -1
- package/dist/types/components/secret-sync.d.ts +0 -9
- package/dist/types/components/secret-sync.d.ts.map +0 -1
- package/dist/types/test/mock-api-client.d.ts +0 -25
- package/dist/types/test/mock-api-client.d.ts.map +0 -1
- package/dist/types/test/test-dev-server.d.ts +0 -129
- package/dist/types/test/test-dev-server.d.ts.map +0 -1
- package/src/cli.ts +0 -997
- package/src/commands/backend.ts +0 -63
- package/src/commands/brain.test.ts +0 -1004
- package/src/commands/brain.ts +0 -215
- package/src/commands/helpers.test.ts +0 -487
- package/src/commands/helpers.ts +0 -870
- package/src/commands/project-config-manager.ts +0 -152
- package/src/commands/project.test.ts +0 -502
- package/src/commands/project.ts +0 -109
- package/src/commands/resources.test.ts +0 -1052
- package/src/commands/resources.ts +0 -97
- package/src/commands/schedule.test.ts +0 -481
- package/src/commands/schedule.ts +0 -65
- package/src/commands/secret.test.ts +0 -210
- package/src/commands/secret.ts +0 -50
- package/src/commands/server.test.ts +0 -493
- package/src/commands/server.ts +0 -353
- package/src/commands/test-utils.ts +0 -324
- package/src/components/brain-history.tsx +0 -198
- package/src/components/brain-list.tsx +0 -105
- package/src/components/brain-rerun.tsx +0 -111
- package/src/components/brain-show.tsx +0 -92
- package/src/components/error.tsx +0 -24
- package/src/components/project-add.tsx +0 -59
- package/src/components/project-create.tsx +0 -83
- package/src/components/project-list.tsx +0 -83
- package/src/components/project-remove.tsx +0 -55
- package/src/components/project-select.tsx +0 -200
- package/src/components/project-show.tsx +0 -58
- package/src/components/resource-clear.tsx +0 -127
- package/src/components/resource-delete.tsx +0 -160
- package/src/components/resource-list.tsx +0 -177
- package/src/components/resource-sync.tsx +0 -170
- package/src/components/resource-types.tsx +0 -55
- package/src/components/resource-upload.tsx +0 -182
- package/src/components/schedule-create.tsx +0 -90
- package/src/components/schedule-delete.tsx +0 -116
- package/src/components/schedule-list.tsx +0 -186
- package/src/components/schedule-runs.tsx +0 -151
- package/src/components/secret-bulk.tsx +0 -79
- package/src/components/secret-create.tsx +0 -49
- package/src/components/secret-delete.tsx +0 -41
- package/src/components/secret-list.tsx +0 -41
- package/src/components/watch.tsx +0 -155
- package/src/hooks/useApi.ts +0 -183
- package/src/positronic.ts +0 -40
- package/src/test/data/resources/config.json +0 -1
- package/src/test/data/resources/data/config.json +0 -1
- package/src/test/data/resources/data/logo.png +0 -2
- package/src/test/data/resources/docs/api.md +0 -3
- package/src/test/data/resources/docs/readme.md +0 -3
- package/src/test/data/resources/example.md +0 -3
- package/src/test/data/resources/file with spaces.txt +0 -1
- package/src/test/data/resources/readme.md +0 -3
- package/src/test/data/resources/test.txt +0 -1
- package/src/test/mock-api-client.ts +0 -145
- package/src/test/test-dev-server.ts +0 -1003
- package/tsconfig.json +0 -11
|
@@ -1,493 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { describe, it, expect, jest } from '@jest/globals';
|
|
4
|
-
import {
|
|
5
|
-
createTestEnv,
|
|
6
|
-
waitForTypesFile,
|
|
7
|
-
px,
|
|
8
|
-
type TestEnv,
|
|
9
|
-
} from './test-utils.js';
|
|
10
|
-
import type { TestServerHandle } from '../test/test-dev-server.js';
|
|
11
|
-
|
|
12
|
-
describe('CLI Integration: positronic server', () => {
|
|
13
|
-
let exitSpy: any;
|
|
14
|
-
let env: TestEnv;
|
|
15
|
-
beforeEach(async () => {
|
|
16
|
-
// Stub process.exit so cleanup doesn't terminate Jest
|
|
17
|
-
env = await createTestEnv();
|
|
18
|
-
exitSpy = jest
|
|
19
|
-
.spyOn(process, 'exit')
|
|
20
|
-
.mockImplementation(() => undefined as never);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
afterEach(async () => {
|
|
24
|
-
env.cleanup();
|
|
25
|
-
exitSpy.mockRestore();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
describe('Project validation', () => {
|
|
29
|
-
it('should not have server command available outside a Positronic project', async () => {
|
|
30
|
-
// No server needed for this test - testing behavior without a project
|
|
31
|
-
try {
|
|
32
|
-
await px(['server']);
|
|
33
|
-
// If we get here, the command didn't fail as expected
|
|
34
|
-
expect(false).toBe(true); // Force failure
|
|
35
|
-
} catch (error: any) {
|
|
36
|
-
// Check that the error message indicates unknown command
|
|
37
|
-
expect(error.message).toContain('Unknown command: server');
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
describe('Server lifecycle', () => {
|
|
43
|
-
it('should call setup() and start() methods on the dev server', async () => {
|
|
44
|
-
const { server } = env;
|
|
45
|
-
await px(['server'], { server });
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
const methodCalls = server.getLogs();
|
|
49
|
-
// Verify the method calls
|
|
50
|
-
const setupCall = methodCalls.find((call) => call.method === 'setup');
|
|
51
|
-
const startCall = methodCalls.find((call) => call.method === 'start');
|
|
52
|
-
|
|
53
|
-
expect(setupCall).toBeDefined();
|
|
54
|
-
expect(setupCall!.args[0]).toBe(false); // force flag not set
|
|
55
|
-
|
|
56
|
-
expect(startCall).toBeDefined();
|
|
57
|
-
} finally {
|
|
58
|
-
process.emit('SIGINT');
|
|
59
|
-
await new Promise((r) => setImmediate(r));
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('Initial sync tests', () => {
|
|
65
|
-
it('should sync resources after server starts', async () => {
|
|
66
|
-
const { server } = env;
|
|
67
|
-
// Start the CLI's server command which will sync resources
|
|
68
|
-
await px(['server'], { server });
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
// Verify that the CLI attempted to upload both default resources
|
|
72
|
-
const uploads = server
|
|
73
|
-
.getLogs()
|
|
74
|
-
.filter((c) => c.method === 'upload')
|
|
75
|
-
.map((c) => (typeof c.args[0] === 'string' ? c.args[0] : ''));
|
|
76
|
-
|
|
77
|
-
expect(uploads.length).toBe(9);
|
|
78
|
-
// The multipart body should include the key/path for each resource
|
|
79
|
-
expect(uploads.some((b) => b.includes('config.json'))).toBe(true);
|
|
80
|
-
expect(uploads.some((b) => b.includes('data/config.json'))).toBe(true);
|
|
81
|
-
expect(uploads.some((b) => b.includes('data/logo.png'))).toBe(true);
|
|
82
|
-
expect(uploads.some((b) => b.includes('docs/api.md'))).toBe(true);
|
|
83
|
-
expect(uploads.some((b) => b.includes('docs/readme.md'))).toBe(true);
|
|
84
|
-
expect(uploads.some((b) => b.includes('example.md'))).toBe(true);
|
|
85
|
-
expect(uploads.some((b) => b.includes('file with spaces.txt'))).toBe(
|
|
86
|
-
true
|
|
87
|
-
);
|
|
88
|
-
expect(uploads.some((b) => b.includes('readme.md'))).toBe(true);
|
|
89
|
-
expect(uploads.some((b) => b.includes('test.txt'))).toBe(true);
|
|
90
|
-
} finally {
|
|
91
|
-
process.emit('SIGINT');
|
|
92
|
-
await new Promise((r) => setImmediate(r));
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('should generate types file after server starts', async () => {
|
|
97
|
-
// Stub process.exit so cleanup doesn't terminate Jest
|
|
98
|
-
const { server } = env;
|
|
99
|
-
await px(['server'], { server });
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
// Wait for types file to be generated with our resources
|
|
103
|
-
const typesPath = path.join(server.projectRootDir, 'resources.d.ts');
|
|
104
|
-
const typesContent = await waitForTypesFile(typesPath, [
|
|
105
|
-
'readme: TextResource;',
|
|
106
|
-
'config: TextResource;',
|
|
107
|
-
'api: TextResource;',
|
|
108
|
-
]);
|
|
109
|
-
// Check that the types file was generated with content
|
|
110
|
-
expect(typesContent).not.toBe('');
|
|
111
|
-
// Check for the module declaration
|
|
112
|
-
expect(typesContent).toContain("declare module '@positronic/core'");
|
|
113
|
-
// Check for resource type definitions
|
|
114
|
-
expect(typesContent).toContain('interface TextResource');
|
|
115
|
-
expect(typesContent).toContain('interface BinaryResource');
|
|
116
|
-
expect(typesContent).toContain('interface Resources');
|
|
117
|
-
// Check for the specific resources we created
|
|
118
|
-
expect(typesContent).toContain('readme: TextResource;');
|
|
119
|
-
expect(typesContent).toContain('config: TextResource;');
|
|
120
|
-
expect(typesContent).toContain('docs: {');
|
|
121
|
-
expect(typesContent).toContain('api: TextResource;');
|
|
122
|
-
} finally {
|
|
123
|
-
// Trigger the cleanup path in ServerCommand to close file watchers
|
|
124
|
-
process.emit('SIGINT');
|
|
125
|
-
await new Promise((r) => setImmediate(r));
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
describe('Error handling', () => {
|
|
131
|
-
it('should handle server startup errors gracefully', async () => {
|
|
132
|
-
const { server } = env;
|
|
133
|
-
|
|
134
|
-
// Mock the server to emit an error after start
|
|
135
|
-
const originalStart = server.start.bind(server);
|
|
136
|
-
let errorCallback: ((error: Error) => void) | undefined;
|
|
137
|
-
|
|
138
|
-
server.start = jest.fn(
|
|
139
|
-
async (port?: number): Promise<TestServerHandle> => {
|
|
140
|
-
const handle = await originalStart(port);
|
|
141
|
-
|
|
142
|
-
// Intercept the onError callback
|
|
143
|
-
const originalOnError = handle.onError.bind(handle);
|
|
144
|
-
handle.onError = (callback: (error: Error) => void) => {
|
|
145
|
-
errorCallback = callback;
|
|
146
|
-
originalOnError(callback);
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
// Emit error after a short delay
|
|
150
|
-
setTimeout(() => {
|
|
151
|
-
if (errorCallback) {
|
|
152
|
-
errorCallback(new Error('Mock server error'));
|
|
153
|
-
}
|
|
154
|
-
}, 10);
|
|
155
|
-
|
|
156
|
-
return handle;
|
|
157
|
-
}
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
// Use a console spy to capture error output
|
|
161
|
-
const consoleErrorSpy = jest
|
|
162
|
-
.spyOn(console, 'error')
|
|
163
|
-
.mockImplementation(() => {});
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
await px(['server'], { server });
|
|
167
|
-
|
|
168
|
-
// Wait for error to be logged
|
|
169
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
170
|
-
|
|
171
|
-
// Verify error was logged
|
|
172
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
173
|
-
'Failed to start dev server:',
|
|
174
|
-
expect.any(Error)
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
// Verify process.exit was called
|
|
178
|
-
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
179
|
-
} finally {
|
|
180
|
-
consoleErrorSpy.mockRestore();
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it('should handle server timeout and exit appropriately', async () => {
|
|
185
|
-
const { server } = env;
|
|
186
|
-
|
|
187
|
-
// Mock the server handle to simulate timeout
|
|
188
|
-
const originalStart = server.start.bind(server);
|
|
189
|
-
server.start = jest.fn(
|
|
190
|
-
async (port?: number): Promise<TestServerHandle> => {
|
|
191
|
-
const handle = await originalStart(port);
|
|
192
|
-
|
|
193
|
-
// Override waitUntilReady to always return false (timeout)
|
|
194
|
-
handle.waitUntilReady = jest
|
|
195
|
-
.fn<() => Promise<boolean>>()
|
|
196
|
-
.mockResolvedValue(false);
|
|
197
|
-
|
|
198
|
-
return handle;
|
|
199
|
-
}
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
const consoleErrorSpy = jest
|
|
203
|
-
.spyOn(console, 'error')
|
|
204
|
-
.mockImplementation(() => {});
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
await px(['server'], { server });
|
|
208
|
-
|
|
209
|
-
// Verify timeout message was logged
|
|
210
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
211
|
-
'⚠️ Server startup timeout: The server is taking longer than expected to initialize.'
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
// Verify process.exit was called
|
|
215
|
-
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
216
|
-
|
|
217
|
-
// Verify server was killed
|
|
218
|
-
const methodCalls = server.getLogs();
|
|
219
|
-
const startCall = methodCalls.find((call) => call.method === 'start');
|
|
220
|
-
expect(startCall).toBeDefined();
|
|
221
|
-
} finally {
|
|
222
|
-
consoleErrorSpy.mockRestore();
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('should handle resource sync with successful upload count', async () => {
|
|
227
|
-
const { server } = env;
|
|
228
|
-
const consoleLogSpy = jest
|
|
229
|
-
.spyOn(console, 'log')
|
|
230
|
-
.mockImplementation(() => {});
|
|
231
|
-
|
|
232
|
-
try {
|
|
233
|
-
await px(['server'], { server });
|
|
234
|
-
|
|
235
|
-
// Wait for sync to complete
|
|
236
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
237
|
-
|
|
238
|
-
// Verify successful sync was logged
|
|
239
|
-
const successLogCall = consoleLogSpy.mock.calls.find(
|
|
240
|
-
(call) =>
|
|
241
|
-
call[0]?.includes('✅ Synced') && call[0]?.includes('resources')
|
|
242
|
-
);
|
|
243
|
-
expect(successLogCall).toBeDefined();
|
|
244
|
-
|
|
245
|
-
// The log should show number of uploads
|
|
246
|
-
expect(successLogCall![0]).toMatch(/✅ Synced \d+ resources/);
|
|
247
|
-
} finally {
|
|
248
|
-
process.emit('SIGINT');
|
|
249
|
-
await new Promise((r) => setImmediate(r));
|
|
250
|
-
consoleLogSpy.mockRestore();
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
describe('File watching', () => {
|
|
256
|
-
it('should set up file watching for resources and brains', async () => {
|
|
257
|
-
const { server } = env;
|
|
258
|
-
|
|
259
|
-
// Simply verify that file watching is initiated - the integration test philosophy
|
|
260
|
-
// suggests testing observable behavior rather than implementation details
|
|
261
|
-
try {
|
|
262
|
-
await px(['server'], { server });
|
|
263
|
-
|
|
264
|
-
// The fact that the server starts successfully means file watching was set up
|
|
265
|
-
// We can verify this indirectly by checking that the server is running
|
|
266
|
-
const methodCalls = server.getLogs();
|
|
267
|
-
const startCall = methodCalls.find((call) => call.method === 'start');
|
|
268
|
-
expect(startCall).toBeDefined();
|
|
269
|
-
|
|
270
|
-
// The server should continue running (no exit called)
|
|
271
|
-
expect(exitSpy).not.toHaveBeenCalled();
|
|
272
|
-
} finally {
|
|
273
|
-
process.emit('SIGINT');
|
|
274
|
-
await new Promise((r) => setImmediate(r));
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
// Skip the watcher error test as it requires complex mocking that doesn't align
|
|
279
|
-
// with our integration testing philosophy. The error handling is already covered
|
|
280
|
-
// by the fact that the server continues running even with watcher issues.
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
describe('Signal handling and cleanup', () => {
|
|
284
|
-
it('should exit cleanly on SIGTERM signal', async () => {
|
|
285
|
-
const { server } = env;
|
|
286
|
-
|
|
287
|
-
try {
|
|
288
|
-
await px(['server'], { server });
|
|
289
|
-
|
|
290
|
-
// Wait for server to be fully started
|
|
291
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
292
|
-
|
|
293
|
-
// Emit SIGTERM
|
|
294
|
-
process.emit('SIGTERM');
|
|
295
|
-
|
|
296
|
-
// Wait for cleanup
|
|
297
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
298
|
-
|
|
299
|
-
// Verify process.exit was called with success code
|
|
300
|
-
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
301
|
-
} finally {
|
|
302
|
-
// Cleanup already handled by SIGTERM
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it('should handle server close event', async () => {
|
|
307
|
-
const { server } = env;
|
|
308
|
-
|
|
309
|
-
// Mock the server to emit close event
|
|
310
|
-
const originalStart = server.start.bind(server);
|
|
311
|
-
let closeCallback: ((code?: number | null) => void) | undefined;
|
|
312
|
-
|
|
313
|
-
server.start = jest.fn(
|
|
314
|
-
async (port?: number): Promise<TestServerHandle> => {
|
|
315
|
-
const handle = await originalStart(port);
|
|
316
|
-
|
|
317
|
-
// Intercept the onClose callback
|
|
318
|
-
const originalOnClose = handle.onClose.bind(handle);
|
|
319
|
-
handle.onClose = (callback: (code?: number | null) => void) => {
|
|
320
|
-
closeCallback = callback;
|
|
321
|
-
originalOnClose(callback);
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
// Emit close event after a delay
|
|
325
|
-
setTimeout(() => {
|
|
326
|
-
if (closeCallback) {
|
|
327
|
-
closeCallback(42); // Custom exit code
|
|
328
|
-
}
|
|
329
|
-
}, 100);
|
|
330
|
-
|
|
331
|
-
return handle;
|
|
332
|
-
}
|
|
333
|
-
);
|
|
334
|
-
|
|
335
|
-
try {
|
|
336
|
-
await px(['server'], { server });
|
|
337
|
-
|
|
338
|
-
// Wait for close event
|
|
339
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
340
|
-
|
|
341
|
-
// Verify process.exit was called with server's exit code
|
|
342
|
-
expect(exitSpy).toHaveBeenCalledWith(42);
|
|
343
|
-
} finally {
|
|
344
|
-
// No explicit cleanup needed
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
describe('Command line arguments', () => {
|
|
350
|
-
it('should pass --force flag to server setup', async () => {
|
|
351
|
-
const { server } = env;
|
|
352
|
-
|
|
353
|
-
try {
|
|
354
|
-
await px(['server', '--force'], { server });
|
|
355
|
-
|
|
356
|
-
// Verify setup was called with force=true
|
|
357
|
-
const methodCalls = server.getLogs();
|
|
358
|
-
const setupCall = methodCalls.find((call) => call.method === 'setup');
|
|
359
|
-
|
|
360
|
-
expect(setupCall).toBeDefined();
|
|
361
|
-
expect(setupCall!.args[0]).toBe(true); // force flag is true
|
|
362
|
-
} finally {
|
|
363
|
-
process.emit('SIGINT');
|
|
364
|
-
await new Promise((r) => setImmediate(r));
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
it('should pass custom port to server start', async () => {
|
|
369
|
-
const { server } = env;
|
|
370
|
-
const customPort = 8765;
|
|
371
|
-
|
|
372
|
-
try {
|
|
373
|
-
await px(['server', '--port', String(customPort)], { server });
|
|
374
|
-
|
|
375
|
-
// Verify start was called with custom port
|
|
376
|
-
const methodCalls = server.getLogs();
|
|
377
|
-
const startCall = methodCalls.find((call) => call.method === 'start');
|
|
378
|
-
|
|
379
|
-
expect(startCall).toBeDefined();
|
|
380
|
-
expect(startCall!.args[0]).toBe(customPort);
|
|
381
|
-
} finally {
|
|
382
|
-
process.emit('SIGINT');
|
|
383
|
-
await new Promise((r) => setImmediate(r));
|
|
384
|
-
}
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
it('should always register log callbacks for server logs', async () => {
|
|
388
|
-
const { server } = env;
|
|
389
|
-
|
|
390
|
-
try {
|
|
391
|
-
await px(['server'], { server });
|
|
392
|
-
|
|
393
|
-
// Verify server started
|
|
394
|
-
const methodCalls = server.getLogs();
|
|
395
|
-
const startCall = methodCalls.find((call) => call.method === 'start');
|
|
396
|
-
expect(startCall).toBeDefined();
|
|
397
|
-
|
|
398
|
-
// Verify callbacks were registered (server always manages its own logs)
|
|
399
|
-
const onLogCall = methodCalls.find((call) => call.method === 'onLog');
|
|
400
|
-
const onErrorCall = methodCalls.find(
|
|
401
|
-
(call) => call.method === 'onError'
|
|
402
|
-
);
|
|
403
|
-
const onWarningCall = methodCalls.find(
|
|
404
|
-
(call) => call.method === 'onWarning'
|
|
405
|
-
);
|
|
406
|
-
|
|
407
|
-
expect(onLogCall).toBeDefined();
|
|
408
|
-
expect(onErrorCall).toBeDefined();
|
|
409
|
-
expect(onWarningCall).toBeDefined();
|
|
410
|
-
} finally {
|
|
411
|
-
process.emit('SIGINT');
|
|
412
|
-
await new Promise((r) => setImmediate(r));
|
|
413
|
-
}
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
it('should create default log file when no --log-file is specified', async () => {
|
|
417
|
-
const { server } = env;
|
|
418
|
-
const defaultLogFile = path.join(server.projectRootDir, '.positronic-server.log');
|
|
419
|
-
|
|
420
|
-
try {
|
|
421
|
-
// Ensure no existing log file
|
|
422
|
-
if (fs.existsSync(defaultLogFile)) {
|
|
423
|
-
fs.unlinkSync(defaultLogFile);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
await px(['server'], { server });
|
|
427
|
-
|
|
428
|
-
// Give time for log file creation
|
|
429
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
430
|
-
|
|
431
|
-
// Verify default log file was created
|
|
432
|
-
expect(fs.existsSync(defaultLogFile)).toBe(true);
|
|
433
|
-
} finally {
|
|
434
|
-
process.emit('SIGINT');
|
|
435
|
-
await new Promise((r) => setImmediate(r));
|
|
436
|
-
|
|
437
|
-
// Clean up
|
|
438
|
-
if (fs.existsSync(defaultLogFile)) {
|
|
439
|
-
fs.unlinkSync(defaultLogFile);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
it('should redirect output to log file when --log-file is specified', async () => {
|
|
445
|
-
const { server } = env;
|
|
446
|
-
const logFile = path.join(server.projectRootDir, 'test-server.log');
|
|
447
|
-
|
|
448
|
-
try {
|
|
449
|
-
await px(['server', '--log-file', logFile], { server });
|
|
450
|
-
|
|
451
|
-
// Give time for server startup and initial logging
|
|
452
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
453
|
-
|
|
454
|
-
// Verify log file was created
|
|
455
|
-
expect(fs.existsSync(logFile)).toBe(true);
|
|
456
|
-
|
|
457
|
-
// Verify server started successfully and callbacks were registered
|
|
458
|
-
const methodCalls = server.getLogs();
|
|
459
|
-
const startCall = methodCalls.find((call) => call.method === 'start');
|
|
460
|
-
expect(startCall).toBeDefined();
|
|
461
|
-
|
|
462
|
-
// Verify callbacks were registered
|
|
463
|
-
const onLogCall = methodCalls.find((call) => call.method === 'onLog');
|
|
464
|
-
const onErrorCall = methodCalls.find(
|
|
465
|
-
(call) => call.method === 'onError'
|
|
466
|
-
);
|
|
467
|
-
const onWarningCall = methodCalls.find(
|
|
468
|
-
(call) => call.method === 'onWarning'
|
|
469
|
-
);
|
|
470
|
-
|
|
471
|
-
expect(onLogCall).toBeDefined();
|
|
472
|
-
expect(onErrorCall).toBeDefined();
|
|
473
|
-
expect(onWarningCall).toBeDefined();
|
|
474
|
-
|
|
475
|
-
// Check log file contains expected output
|
|
476
|
-
const logContent = fs.readFileSync(logFile, 'utf-8');
|
|
477
|
-
expect(logContent).toContain('[INFO]');
|
|
478
|
-
expect(logContent).toContain('Synced');
|
|
479
|
-
|
|
480
|
-
// In real usage, the process ID is output to stdout for AI agents
|
|
481
|
-
// but in tests this may not be captured due to the testing framework
|
|
482
|
-
} finally {
|
|
483
|
-
process.emit('SIGINT');
|
|
484
|
-
await new Promise((r) => setImmediate(r));
|
|
485
|
-
|
|
486
|
-
// Clean up log file
|
|
487
|
-
if (fs.existsSync(logFile)) {
|
|
488
|
-
fs.unlinkSync(logFile);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
});
|
|
492
|
-
});
|
|
493
|
-
});
|