@positronic/cli 0.0.3 → 0.0.5
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 +57 -27
- 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,1052 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from '@jest/globals';
|
|
2
|
-
import { createTestEnv, px, waitForTypesFile } from './test-utils.js';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
|
|
6
|
-
describe('CLI Integration: positronic resources types', () => {
|
|
7
|
-
it('should generate type definitions for resources', async () => {
|
|
8
|
-
const env = await createTestEnv();
|
|
9
|
-
const px = await env.start();
|
|
10
|
-
try {
|
|
11
|
-
const { waitForOutput, waitForTypesFile } = await px([
|
|
12
|
-
'resources',
|
|
13
|
-
'sync',
|
|
14
|
-
]);
|
|
15
|
-
const isOutputRendered = await waitForOutput(/sync summary/i, 50);
|
|
16
|
-
const typesContent = await waitForTypesFile('config: TextResource;');
|
|
17
|
-
expect(isOutputRendered).toBe(true);
|
|
18
|
-
const normalizeWhitespace = (str: string) =>
|
|
19
|
-
str.replace(/\s+/g, ' ').trim();
|
|
20
|
-
|
|
21
|
-
const expectedContent = `declare module '@positronic/core' {
|
|
22
|
-
interface TextResource {
|
|
23
|
-
load(): Promise<string>;
|
|
24
|
-
}
|
|
25
|
-
interface BinaryResource {
|
|
26
|
-
load(): Promise<Buffer>;
|
|
27
|
-
}
|
|
28
|
-
interface Resources {
|
|
29
|
-
// Method signatures for loading resources by path
|
|
30
|
-
loadText(path: string): Promise<string>;
|
|
31
|
-
loadBinary(path: string): Promise<Buffer>;
|
|
32
|
-
// Resource properties accessible via dot notation
|
|
33
|
-
config: TextResource;
|
|
34
|
-
data: {
|
|
35
|
-
config: TextResource;
|
|
36
|
-
logo: BinaryResource;
|
|
37
|
-
};
|
|
38
|
-
docs: {
|
|
39
|
-
api: TextResource;
|
|
40
|
-
readme: TextResource;
|
|
41
|
-
};
|
|
42
|
-
example: TextResource;
|
|
43
|
-
readme: TextResource;
|
|
44
|
-
test: TextResource;
|
|
45
|
-
}
|
|
46
|
-
}`;
|
|
47
|
-
|
|
48
|
-
expect(normalizeWhitespace(typesContent)).toContain(
|
|
49
|
-
normalizeWhitespace(expectedContent)
|
|
50
|
-
);
|
|
51
|
-
} finally {
|
|
52
|
-
await env.stopAndCleanup();
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('should handle empty resources directory', async () => {
|
|
57
|
-
const env = await createTestEnv();
|
|
58
|
-
const px = await env.start();
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
// Remove the default resources created by createMinimalProject
|
|
62
|
-
const resourcesDir = path.join(env.projectRootDir, 'resources');
|
|
63
|
-
|
|
64
|
-
// Remove all contents recursively and recreate empty directory
|
|
65
|
-
fs.rmSync(resourcesDir, { recursive: true, force: true });
|
|
66
|
-
fs.mkdirSync(resourcesDir);
|
|
67
|
-
|
|
68
|
-
// Verify directory is empty
|
|
69
|
-
expect(fs.readdirSync(resourcesDir).length).toBe(0);
|
|
70
|
-
|
|
71
|
-
// Run the sync command using cli
|
|
72
|
-
const syncElement = await px(['resources', 'sync']);
|
|
73
|
-
const isOutputRendered = await syncElement.waitForOutput(
|
|
74
|
-
/no files found in the resources directory/i
|
|
75
|
-
);
|
|
76
|
-
expect(isOutputRendered).toBe(true);
|
|
77
|
-
const typesContent = await syncElement.waitForTypesFile(
|
|
78
|
-
'loadText(path: string): Promise<string>;'
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
// Check if the types file was generated
|
|
82
|
-
expect(typesContent).toBeDefined();
|
|
83
|
-
|
|
84
|
-
// Should still have the basic structure
|
|
85
|
-
expect(typesContent).toContain("declare module '@positronic/core'");
|
|
86
|
-
expect(typesContent).toContain('interface Resources');
|
|
87
|
-
expect(typesContent).toContain('loadText(path: string): Promise<string>');
|
|
88
|
-
expect(typesContent).toContain(
|
|
89
|
-
'loadBinary(path: string): Promise<Buffer>'
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
// Run the list command
|
|
93
|
-
const listElement = await px(['resources', 'list']);
|
|
94
|
-
|
|
95
|
-
const resourceListOutput = await listElement.waitForOutput(
|
|
96
|
-
/no resources found in the project/i
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
expect(resourceListOutput).toBe(true);
|
|
100
|
-
} finally {
|
|
101
|
-
await env.stopAndCleanup();
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('should handle resources with special characters', async () => {
|
|
106
|
-
const env = await createTestEnv();
|
|
107
|
-
|
|
108
|
-
// Setup files with special characters
|
|
109
|
-
env.setup((dir: string) => {
|
|
110
|
-
// Create resources directory
|
|
111
|
-
const resourcesDir = path.join(dir, 'resources');
|
|
112
|
-
|
|
113
|
-
// Remove existing resources directory if it exists
|
|
114
|
-
if (fs.existsSync(resourcesDir)) {
|
|
115
|
-
fs.rmSync(resourcesDir, { recursive: true, force: true });
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Create fresh resources directory
|
|
119
|
-
fs.mkdirSync(resourcesDir, { recursive: true });
|
|
120
|
-
|
|
121
|
-
// Create files with special characters
|
|
122
|
-
fs.writeFileSync(path.join(resourcesDir, 'valid_file.txt'), 'content');
|
|
123
|
-
fs.writeFileSync(path.join(resourcesDir, '$special.txt'), 'content'); // Valid JS identifier
|
|
124
|
-
fs.writeFileSync(path.join(resourcesDir, '_underscore.txt'), 'content'); // Valid JS identifier
|
|
125
|
-
fs.writeFileSync(path.join(resourcesDir, '123invalid.txt'), 'content'); // Invalid - starts with number
|
|
126
|
-
fs.writeFileSync(
|
|
127
|
-
path.join(resourcesDir, 'special-chars!@#.txt'),
|
|
128
|
-
'content'
|
|
129
|
-
); // Invalid
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const px = await env.start();
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
// Run the sync command first (types are generated as part of sync)
|
|
136
|
-
const { waitForOutput, waitForTypesFile } = await px([
|
|
137
|
-
'resources',
|
|
138
|
-
'sync',
|
|
139
|
-
]);
|
|
140
|
-
const isOutputRendered = await waitForOutput(/sync summary/i);
|
|
141
|
-
expect(isOutputRendered).toBe(true);
|
|
142
|
-
|
|
143
|
-
// Wait for types file to be generated
|
|
144
|
-
const typesContent = await waitForTypesFile([
|
|
145
|
-
'valid_file: TextResource;',
|
|
146
|
-
'$special: TextResource;',
|
|
147
|
-
'_underscore: TextResource;',
|
|
148
|
-
]);
|
|
149
|
-
|
|
150
|
-
// Verify the generated content
|
|
151
|
-
expect(typesContent).toBeDefined();
|
|
152
|
-
|
|
153
|
-
// Check valid identifiers are included
|
|
154
|
-
expect(typesContent).toContain('valid_file: TextResource;');
|
|
155
|
-
expect(typesContent).toContain('$special: TextResource;');
|
|
156
|
-
expect(typesContent).toContain('_underscore: TextResource;');
|
|
157
|
-
|
|
158
|
-
// Check invalid identifiers are excluded
|
|
159
|
-
expect(typesContent).not.toContain('123invalid');
|
|
160
|
-
expect(typesContent).not.toContain('special-chars');
|
|
161
|
-
} finally {
|
|
162
|
-
await env.stopAndCleanup();
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('should correctly identify text vs binary files', async () => {
|
|
167
|
-
const env = await createTestEnv();
|
|
168
|
-
|
|
169
|
-
// Setup files with various types
|
|
170
|
-
env.setup((dir: string) => {
|
|
171
|
-
// Create resources directory
|
|
172
|
-
const resourcesDir = path.join(dir, 'resources');
|
|
173
|
-
|
|
174
|
-
// Remove existing resources directory if it exists
|
|
175
|
-
if (fs.existsSync(resourcesDir)) {
|
|
176
|
-
fs.rmSync(resourcesDir, { recursive: true, force: true });
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Create fresh resources directory
|
|
180
|
-
fs.mkdirSync(resourcesDir, { recursive: true });
|
|
181
|
-
|
|
182
|
-
// Create various file types
|
|
183
|
-
fs.writeFileSync(path.join(resourcesDir, 'text.txt'), 'text');
|
|
184
|
-
fs.writeFileSync(path.join(resourcesDir, 'script.js'), 'code');
|
|
185
|
-
fs.writeFileSync(path.join(resourcesDir, 'config.json'), '{}');
|
|
186
|
-
fs.writeFileSync(path.join(resourcesDir, 'styles.css'), 'css');
|
|
187
|
-
|
|
188
|
-
// Create actual binary content for binary files
|
|
189
|
-
// JPEG magic bytes
|
|
190
|
-
const jpegHeader = Buffer.from([
|
|
191
|
-
0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46,
|
|
192
|
-
]);
|
|
193
|
-
fs.writeFileSync(path.join(resourcesDir, 'image.jpg'), jpegHeader);
|
|
194
|
-
|
|
195
|
-
// Random binary data
|
|
196
|
-
const binaryData = Buffer.from([
|
|
197
|
-
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
|
|
198
|
-
]);
|
|
199
|
-
fs.writeFileSync(path.join(resourcesDir, 'binary.bin'), binaryData);
|
|
200
|
-
|
|
201
|
-
// PDF magic bytes
|
|
202
|
-
const pdfHeader = Buffer.from('%PDF-1.4\n%âÌÊÓ\n');
|
|
203
|
-
fs.writeFileSync(path.join(resourcesDir, 'document.pdf'), pdfHeader);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
const px = await env.start();
|
|
207
|
-
|
|
208
|
-
try {
|
|
209
|
-
// Run the sync command first (types are generated as part of sync)
|
|
210
|
-
const { waitForOutput, waitForTypesFile } = await px([
|
|
211
|
-
'resources',
|
|
212
|
-
'sync',
|
|
213
|
-
]);
|
|
214
|
-
|
|
215
|
-
const isOutputRendered = await waitForOutput(/sync summary/i);
|
|
216
|
-
expect(isOutputRendered).toBe(true);
|
|
217
|
-
|
|
218
|
-
// Wait for types file to be generated with expected content
|
|
219
|
-
const typesContent = await waitForTypesFile([
|
|
220
|
-
'text: TextResource;',
|
|
221
|
-
'script: TextResource;',
|
|
222
|
-
'config: TextResource;',
|
|
223
|
-
'styles: TextResource;',
|
|
224
|
-
'image: BinaryResource;',
|
|
225
|
-
'binary: BinaryResource;',
|
|
226
|
-
'document: BinaryResource;',
|
|
227
|
-
]);
|
|
228
|
-
|
|
229
|
-
// Verify the generated content
|
|
230
|
-
expect(typesContent).toBeDefined();
|
|
231
|
-
|
|
232
|
-
// Check text resources
|
|
233
|
-
expect(typesContent).toContain('text: TextResource;');
|
|
234
|
-
expect(typesContent).toContain('script: TextResource;');
|
|
235
|
-
expect(typesContent).toContain('config: TextResource;');
|
|
236
|
-
expect(typesContent).toContain('styles: TextResource;');
|
|
237
|
-
|
|
238
|
-
// Check binary resources
|
|
239
|
-
expect(typesContent).toContain('image: BinaryResource;');
|
|
240
|
-
expect(typesContent).toContain('binary: BinaryResource;');
|
|
241
|
-
expect(typesContent).toContain('document: BinaryResource;');
|
|
242
|
-
} finally {
|
|
243
|
-
await env.stopAndCleanup();
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
describe('resources list command', () => {
|
|
248
|
-
it('should handle empty resources', async () => {
|
|
249
|
-
const env = await createTestEnv();
|
|
250
|
-
const px = await env.start();
|
|
251
|
-
|
|
252
|
-
try {
|
|
253
|
-
const { waitForOutput } = await px(['resources', 'list']);
|
|
254
|
-
const found = await waitForOutput(/No resources found in the project/);
|
|
255
|
-
expect(found).toBe(true);
|
|
256
|
-
} finally {
|
|
257
|
-
await env.stopAndCleanup();
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it('should display resources with tree structure', async () => {
|
|
262
|
-
const env = await createTestEnv();
|
|
263
|
-
|
|
264
|
-
// Add resources with nested structure
|
|
265
|
-
env.server.addResource({
|
|
266
|
-
key: 'readme.txt',
|
|
267
|
-
type: 'text',
|
|
268
|
-
size: 1024,
|
|
269
|
-
lastModified: new Date().toISOString(),
|
|
270
|
-
local: true,
|
|
271
|
-
});
|
|
272
|
-
env.server.addResource({
|
|
273
|
-
key: 'data/config.json',
|
|
274
|
-
type: 'text',
|
|
275
|
-
size: 2048,
|
|
276
|
-
lastModified: new Date().toISOString(),
|
|
277
|
-
local: true,
|
|
278
|
-
});
|
|
279
|
-
env.server.addResource({
|
|
280
|
-
key: 'data/users/admin.json',
|
|
281
|
-
type: 'text',
|
|
282
|
-
size: 512,
|
|
283
|
-
lastModified: new Date().toISOString(),
|
|
284
|
-
local: true,
|
|
285
|
-
});
|
|
286
|
-
env.server.addResource({
|
|
287
|
-
key: 'images/logo.png',
|
|
288
|
-
type: 'binary',
|
|
289
|
-
size: 10240,
|
|
290
|
-
lastModified: new Date().toISOString(),
|
|
291
|
-
local: false,
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
const px = await env.start();
|
|
295
|
-
|
|
296
|
-
try {
|
|
297
|
-
const { waitForOutput } = await px(['resources', 'list']);
|
|
298
|
-
|
|
299
|
-
// Check tree structure is displayed
|
|
300
|
-
const foundTree = await waitForOutput(/resources/);
|
|
301
|
-
expect(foundTree).toBe(true);
|
|
302
|
-
|
|
303
|
-
// Check for files in root
|
|
304
|
-
const foundReadme = await waitForOutput(/readme\.txt/);
|
|
305
|
-
expect(foundReadme).toBe(true);
|
|
306
|
-
|
|
307
|
-
// Check for nested directories
|
|
308
|
-
const foundData = await waitForOutput(/data/);
|
|
309
|
-
expect(foundData).toBe(true);
|
|
310
|
-
|
|
311
|
-
const foundConfig = await waitForOutput(/config\.json/);
|
|
312
|
-
expect(foundConfig).toBe(true);
|
|
313
|
-
|
|
314
|
-
// Check for deeply nested
|
|
315
|
-
const foundUsers = await waitForOutput(/users/);
|
|
316
|
-
expect(foundUsers).toBe(true);
|
|
317
|
-
|
|
318
|
-
const foundAdmin = await waitForOutput(/admin\.json/);
|
|
319
|
-
expect(foundAdmin).toBe(true);
|
|
320
|
-
|
|
321
|
-
// Check for images directory
|
|
322
|
-
const foundImages = await waitForOutput(/images/);
|
|
323
|
-
expect(foundImages).toBe(true);
|
|
324
|
-
|
|
325
|
-
const foundLogo = await waitForOutput(/logo\.png/);
|
|
326
|
-
expect(foundLogo).toBe(true);
|
|
327
|
-
} finally {
|
|
328
|
-
await env.stopAndCleanup();
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
it('should display file sizes with correct formatting', async () => {
|
|
333
|
-
const env = await createTestEnv();
|
|
334
|
-
|
|
335
|
-
// Add resources with various sizes
|
|
336
|
-
env.server.addResource({
|
|
337
|
-
key: 'small.txt',
|
|
338
|
-
type: 'text',
|
|
339
|
-
size: 500, // 500 bytes
|
|
340
|
-
lastModified: new Date().toISOString(),
|
|
341
|
-
local: true,
|
|
342
|
-
});
|
|
343
|
-
env.server.addResource({
|
|
344
|
-
key: 'medium.txt',
|
|
345
|
-
type: 'text',
|
|
346
|
-
size: 1536, // 1.5 KB
|
|
347
|
-
lastModified: new Date().toISOString(),
|
|
348
|
-
local: true,
|
|
349
|
-
});
|
|
350
|
-
env.server.addResource({
|
|
351
|
-
key: 'large.bin',
|
|
352
|
-
type: 'binary',
|
|
353
|
-
size: 1048576, // 1 MB
|
|
354
|
-
lastModified: new Date().toISOString(),
|
|
355
|
-
local: true,
|
|
356
|
-
});
|
|
357
|
-
env.server.addResource({
|
|
358
|
-
key: 'huge.bin',
|
|
359
|
-
type: 'binary',
|
|
360
|
-
size: 10485760, // 10 MB
|
|
361
|
-
lastModified: new Date().toISOString(),
|
|
362
|
-
local: true,
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
const px = await env.start();
|
|
366
|
-
|
|
367
|
-
try {
|
|
368
|
-
const { waitForOutput } = await px(['resources', 'list']);
|
|
369
|
-
|
|
370
|
-
// Check size formatting
|
|
371
|
-
const foundSmall = await waitForOutput(/small\.txt.*500 B/);
|
|
372
|
-
expect(foundSmall).toBe(true);
|
|
373
|
-
|
|
374
|
-
const foundMedium = await waitForOutput(/medium\.txt.*1\.5 KB/);
|
|
375
|
-
expect(foundMedium).toBe(true);
|
|
376
|
-
|
|
377
|
-
const foundLarge = await waitForOutput(/large\.bin.*1\.0 MB/);
|
|
378
|
-
expect(foundLarge).toBe(true);
|
|
379
|
-
|
|
380
|
-
const foundHuge = await waitForOutput(/huge\.bin.*10\.0 MB/);
|
|
381
|
-
expect(foundHuge).toBe(true);
|
|
382
|
-
} finally {
|
|
383
|
-
await env.stopAndCleanup();
|
|
384
|
-
}
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
it('should indicate local vs remote resources', async () => {
|
|
388
|
-
const env = await createTestEnv();
|
|
389
|
-
|
|
390
|
-
// Add mix of local and remote resources
|
|
391
|
-
env.server.addResource({
|
|
392
|
-
key: 'local-file.txt',
|
|
393
|
-
type: 'text',
|
|
394
|
-
size: 100,
|
|
395
|
-
lastModified: new Date().toISOString(),
|
|
396
|
-
local: true,
|
|
397
|
-
});
|
|
398
|
-
env.server.addResource({
|
|
399
|
-
key: 'remote-file.txt',
|
|
400
|
-
type: 'text',
|
|
401
|
-
size: 200,
|
|
402
|
-
lastModified: new Date().toISOString(),
|
|
403
|
-
local: false,
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
const px = await env.start();
|
|
407
|
-
|
|
408
|
-
try {
|
|
409
|
-
const { waitForOutput } = await px(['resources', 'list']);
|
|
410
|
-
|
|
411
|
-
// Check both files are listed
|
|
412
|
-
const foundLocal = await waitForOutput(/local-file\.txt/);
|
|
413
|
-
expect(foundLocal).toBe(true);
|
|
414
|
-
|
|
415
|
-
const foundRemote = await waitForOutput(/remote-file\.txt/);
|
|
416
|
-
expect(foundRemote).toBe(true);
|
|
417
|
-
|
|
418
|
-
// Remote resources should have arrow indicator
|
|
419
|
-
const foundArrow = await waitForOutput(/↗/);
|
|
420
|
-
expect(foundArrow).toBe(true);
|
|
421
|
-
|
|
422
|
-
// Should show legend for uploaded resources
|
|
423
|
-
const foundLegend = await waitForOutput(
|
|
424
|
-
/uploaded resource \(not in local filesystem\)/
|
|
425
|
-
);
|
|
426
|
-
expect(foundLegend).toBe(true);
|
|
427
|
-
} finally {
|
|
428
|
-
await env.stopAndCleanup();
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
it('should show loading state', async () => {
|
|
433
|
-
const env = await createTestEnv();
|
|
434
|
-
const px = await env.start();
|
|
435
|
-
|
|
436
|
-
try {
|
|
437
|
-
// Stop server to simulate slow loading
|
|
438
|
-
env.server.stop();
|
|
439
|
-
|
|
440
|
-
const { waitForOutput, instance } = await px(['resources', 'list']);
|
|
441
|
-
|
|
442
|
-
// Should show loading message initially
|
|
443
|
-
const foundLoading = await waitForOutput(/Loading resources\.\.\./);
|
|
444
|
-
expect(foundLoading).toBe(true);
|
|
445
|
-
|
|
446
|
-
// Should eventually show error when server is down
|
|
447
|
-
const foundError = await waitForOutput(/Error connecting/);
|
|
448
|
-
expect(foundError).toBe(true);
|
|
449
|
-
} finally {
|
|
450
|
-
// Cleanup without stopping server (already stopped)
|
|
451
|
-
env.cleanup();
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
it('should display resource count summary', async () => {
|
|
456
|
-
const env = await createTestEnv();
|
|
457
|
-
|
|
458
|
-
// Add multiple resources
|
|
459
|
-
for (let i = 1; i <= 5; i++) {
|
|
460
|
-
env.server.addResource({
|
|
461
|
-
key: `file${i}.txt`,
|
|
462
|
-
type: 'text',
|
|
463
|
-
size: 100 * i,
|
|
464
|
-
lastModified: new Date().toISOString(),
|
|
465
|
-
local: i % 2 === 0, // Even numbers are local
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
const px = await env.start();
|
|
470
|
-
|
|
471
|
-
try {
|
|
472
|
-
const { waitForOutput } = await px(['resources', 'list']);
|
|
473
|
-
|
|
474
|
-
// Should show count in format "Found X resources:"
|
|
475
|
-
const foundCount = await waitForOutput(/Found 5 resources:/);
|
|
476
|
-
expect(foundCount).toBe(true);
|
|
477
|
-
} finally {
|
|
478
|
-
await env.stopAndCleanup();
|
|
479
|
-
}
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
it('should show single resource properly', async () => {
|
|
483
|
-
const env = await createTestEnv();
|
|
484
|
-
|
|
485
|
-
// Add a single resource to test singular form
|
|
486
|
-
env.server.addResource({
|
|
487
|
-
key: 'single.txt',
|
|
488
|
-
type: 'text',
|
|
489
|
-
size: 42,
|
|
490
|
-
lastModified: new Date().toISOString(),
|
|
491
|
-
local: true,
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
const px = await env.start();
|
|
495
|
-
|
|
496
|
-
try {
|
|
497
|
-
const { waitForOutput } = await px(['resources', 'list']);
|
|
498
|
-
|
|
499
|
-
// Should show "Found 1 resource:" (singular)
|
|
500
|
-
const foundCount = await waitForOutput(/Found 1 resource:/);
|
|
501
|
-
expect(foundCount).toBe(true);
|
|
502
|
-
} finally {
|
|
503
|
-
await env.stopAndCleanup();
|
|
504
|
-
}
|
|
505
|
-
});
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
describe('resources delete command', () => {
|
|
509
|
-
it('should show error when resource does not exist', async () => {
|
|
510
|
-
const env = await createTestEnv();
|
|
511
|
-
const px = await env.start();
|
|
512
|
-
|
|
513
|
-
try {
|
|
514
|
-
const { waitForOutput } = await px([
|
|
515
|
-
'resources',
|
|
516
|
-
'upload',
|
|
517
|
-
'-d',
|
|
518
|
-
'non-existent.txt',
|
|
519
|
-
]);
|
|
520
|
-
|
|
521
|
-
// Should show checking state
|
|
522
|
-
const foundChecking = await waitForOutput(/Checking resource\.\.\./);
|
|
523
|
-
expect(foundChecking).toBe(true);
|
|
524
|
-
|
|
525
|
-
// Should show warning for non-existent resource
|
|
526
|
-
const foundWarning = await waitForOutput(
|
|
527
|
-
/Warning: This will permanently delete/
|
|
528
|
-
);
|
|
529
|
-
expect(foundWarning).toBe(true);
|
|
530
|
-
} finally {
|
|
531
|
-
await env.stopAndCleanup();
|
|
532
|
-
}
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
it('should prevent deletion of local resources', async () => {
|
|
536
|
-
const env = await createTestEnv();
|
|
537
|
-
|
|
538
|
-
// Add a local resource
|
|
539
|
-
env.server.addResource({
|
|
540
|
-
key: 'local-file.txt',
|
|
541
|
-
type: 'text',
|
|
542
|
-
size: 100,
|
|
543
|
-
lastModified: new Date().toISOString(),
|
|
544
|
-
local: true,
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
const px = await env.start();
|
|
548
|
-
|
|
549
|
-
try {
|
|
550
|
-
const { waitForOutput } = await px([
|
|
551
|
-
'resources',
|
|
552
|
-
'upload',
|
|
553
|
-
'-d',
|
|
554
|
-
'local-file.txt',
|
|
555
|
-
]);
|
|
556
|
-
|
|
557
|
-
// Should show error about local resource
|
|
558
|
-
const foundError = await waitForOutput(/Cannot Delete Local Resource/);
|
|
559
|
-
expect(foundError).toBe(true);
|
|
560
|
-
|
|
561
|
-
// Should show explanation
|
|
562
|
-
const foundExplanation = await waitForOutput(
|
|
563
|
-
/This resource was synced from your local filesystem/
|
|
564
|
-
);
|
|
565
|
-
expect(foundExplanation).toBe(true);
|
|
566
|
-
|
|
567
|
-
// Should show instructions
|
|
568
|
-
const foundInstructions = await waitForOutput(
|
|
569
|
-
/delete the file locally and run 'px resources sync'/
|
|
570
|
-
);
|
|
571
|
-
expect(foundInstructions).toBe(true);
|
|
572
|
-
} finally {
|
|
573
|
-
await env.stopAndCleanup();
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
it('should show confirmation prompt for remote resources', async () => {
|
|
578
|
-
const env = await createTestEnv();
|
|
579
|
-
|
|
580
|
-
// Add a remote resource
|
|
581
|
-
env.server.addResource({
|
|
582
|
-
key: 'remote-file.txt',
|
|
583
|
-
type: 'text',
|
|
584
|
-
size: 200,
|
|
585
|
-
lastModified: new Date().toISOString(),
|
|
586
|
-
local: false,
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
const px = await env.start();
|
|
590
|
-
|
|
591
|
-
try {
|
|
592
|
-
const { waitForOutput } = await px([
|
|
593
|
-
'resources',
|
|
594
|
-
'upload',
|
|
595
|
-
'-d',
|
|
596
|
-
'remote-file.txt',
|
|
597
|
-
]);
|
|
598
|
-
|
|
599
|
-
// Should show warning
|
|
600
|
-
const foundWarning = await waitForOutput(
|
|
601
|
-
/Warning: This will permanently delete the following resource/
|
|
602
|
-
);
|
|
603
|
-
expect(foundWarning).toBe(true);
|
|
604
|
-
|
|
605
|
-
// Should show the resource path
|
|
606
|
-
const foundPath = await waitForOutput(/remote-file\.txt/);
|
|
607
|
-
expect(foundPath).toBe(true);
|
|
608
|
-
|
|
609
|
-
// Should show confirmation prompt
|
|
610
|
-
const foundPrompt = await waitForOutput(
|
|
611
|
-
/Type "yes" to confirm deletion:/
|
|
612
|
-
);
|
|
613
|
-
expect(foundPrompt).toBe(true);
|
|
614
|
-
} finally {
|
|
615
|
-
await env.stopAndCleanup();
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
it('should delete resource when using force flag', async () => {
|
|
620
|
-
const env = await createTestEnv();
|
|
621
|
-
|
|
622
|
-
// Add a remote resource BEFORE starting the server
|
|
623
|
-
env.server.addResource({
|
|
624
|
-
key: 'to-delete.txt',
|
|
625
|
-
type: 'text',
|
|
626
|
-
size: 300,
|
|
627
|
-
lastModified: new Date().toISOString(),
|
|
628
|
-
local: false,
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
const px = await env.start();
|
|
632
|
-
|
|
633
|
-
try {
|
|
634
|
-
const { waitForOutput } = await px([
|
|
635
|
-
'resources',
|
|
636
|
-
'upload',
|
|
637
|
-
'-d',
|
|
638
|
-
'-f',
|
|
639
|
-
'to-delete.txt',
|
|
640
|
-
]);
|
|
641
|
-
|
|
642
|
-
// Should check resource first
|
|
643
|
-
const foundChecking = await waitForOutput(/Checking resource\.\.\./);
|
|
644
|
-
expect(foundChecking).toBe(true);
|
|
645
|
-
|
|
646
|
-
// Should show success message (may skip the deleting message due to speed)
|
|
647
|
-
const foundSuccess = await waitForOutput(
|
|
648
|
-
/✅ Successfully deleted: to-delete\.txt/
|
|
649
|
-
);
|
|
650
|
-
expect(foundSuccess).toBe(true);
|
|
651
|
-
|
|
652
|
-
// Verify the resource was actually deleted
|
|
653
|
-
const calls = env.server.getLogs();
|
|
654
|
-
const deleteCall = calls.find(
|
|
655
|
-
(c) => c.method === 'deleteResource' && c.args[0] === 'to-delete.txt'
|
|
656
|
-
);
|
|
657
|
-
expect(deleteCall).toBeDefined();
|
|
658
|
-
} finally {
|
|
659
|
-
await env.stopAndCleanup();
|
|
660
|
-
}
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
it('should still prevent deletion of local resources with force flag', async () => {
|
|
664
|
-
const env = await createTestEnv();
|
|
665
|
-
|
|
666
|
-
// Add a local resource
|
|
667
|
-
env.server.addResource({
|
|
668
|
-
key: 'local-file.txt',
|
|
669
|
-
type: 'text',
|
|
670
|
-
size: 100,
|
|
671
|
-
lastModified: new Date().toISOString(),
|
|
672
|
-
local: true,
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
const px = await env.start();
|
|
676
|
-
|
|
677
|
-
try {
|
|
678
|
-
const { waitForOutput } = await px([
|
|
679
|
-
'resources',
|
|
680
|
-
'upload',
|
|
681
|
-
'-d',
|
|
682
|
-
'-f',
|
|
683
|
-
'local-file.txt',
|
|
684
|
-
]);
|
|
685
|
-
|
|
686
|
-
// Should still show error about local resource even with force flag
|
|
687
|
-
const foundError = await waitForOutput(/Cannot Delete Local Resource/);
|
|
688
|
-
expect(foundError).toBe(true);
|
|
689
|
-
} finally {
|
|
690
|
-
await env.stopAndCleanup();
|
|
691
|
-
}
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
it('should handle API connection errors', async () => {
|
|
695
|
-
const env = await createTestEnv();
|
|
696
|
-
// Don't start the server to simulate connection error
|
|
697
|
-
|
|
698
|
-
try {
|
|
699
|
-
const { waitForOutput } = await px(
|
|
700
|
-
['resources', 'upload', '-d', 'any-resource.txt'],
|
|
701
|
-
{ server: env.server }
|
|
702
|
-
);
|
|
703
|
-
|
|
704
|
-
// Should show checking message first
|
|
705
|
-
const foundChecking = await waitForOutput(/Checking resource\.\.\./);
|
|
706
|
-
expect(foundChecking).toBe(true);
|
|
707
|
-
|
|
708
|
-
// Should eventually show connection error
|
|
709
|
-
const foundError = await waitForOutput(/Error connecting/);
|
|
710
|
-
expect(foundError).toBe(true);
|
|
711
|
-
} finally {
|
|
712
|
-
// Just cleanup temp dir since server was never started
|
|
713
|
-
}
|
|
714
|
-
});
|
|
715
|
-
|
|
716
|
-
it('should handle nested resource paths with force flag', async () => {
|
|
717
|
-
const env = await createTestEnv();
|
|
718
|
-
|
|
719
|
-
// Add a nested resource
|
|
720
|
-
env.server.addResource({
|
|
721
|
-
key: 'folder/subfolder/nested-file.txt',
|
|
722
|
-
type: 'text',
|
|
723
|
-
size: 800,
|
|
724
|
-
lastModified: new Date().toISOString(),
|
|
725
|
-
local: false,
|
|
726
|
-
});
|
|
727
|
-
|
|
728
|
-
const px = await env.start();
|
|
729
|
-
|
|
730
|
-
try {
|
|
731
|
-
const { waitForOutput } = await px([
|
|
732
|
-
'resources',
|
|
733
|
-
'upload',
|
|
734
|
-
'-d',
|
|
735
|
-
'-f',
|
|
736
|
-
'folder/subfolder/nested-file.txt',
|
|
737
|
-
]);
|
|
738
|
-
|
|
739
|
-
// Should delete successfully
|
|
740
|
-
const foundSuccess = await waitForOutput(
|
|
741
|
-
/✅ Successfully deleted: folder\/subfolder\/nested-file\.txt/
|
|
742
|
-
);
|
|
743
|
-
expect(foundSuccess).toBe(true);
|
|
744
|
-
|
|
745
|
-
// Verify the resource was deleted
|
|
746
|
-
const calls = env.server.getLogs();
|
|
747
|
-
const deleteCall = calls.find(
|
|
748
|
-
(c) =>
|
|
749
|
-
c.method === 'deleteResource' &&
|
|
750
|
-
c.args[0] === 'folder/subfolder/nested-file.txt'
|
|
751
|
-
);
|
|
752
|
-
expect(deleteCall).toBeDefined();
|
|
753
|
-
} finally {
|
|
754
|
-
await env.stopAndCleanup();
|
|
755
|
-
}
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
it('should handle nested resource paths without force flag', async () => {
|
|
759
|
-
const env = await createTestEnv();
|
|
760
|
-
|
|
761
|
-
// Add a nested resource
|
|
762
|
-
env.server.addResource({
|
|
763
|
-
key: 'folder/nested.txt',
|
|
764
|
-
type: 'text',
|
|
765
|
-
size: 400,
|
|
766
|
-
lastModified: new Date().toISOString(),
|
|
767
|
-
local: false,
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
const px = await env.start();
|
|
771
|
-
|
|
772
|
-
try {
|
|
773
|
-
const { waitForOutput } = await px([
|
|
774
|
-
'resources',
|
|
775
|
-
'upload',
|
|
776
|
-
'-d',
|
|
777
|
-
'folder/nested.txt',
|
|
778
|
-
]);
|
|
779
|
-
|
|
780
|
-
// Should show the full nested path in the warning
|
|
781
|
-
const foundPath = await waitForOutput(/folder\/nested\.txt/);
|
|
782
|
-
expect(foundPath).toBe(true);
|
|
783
|
-
|
|
784
|
-
// Should show confirmation prompt
|
|
785
|
-
const foundPrompt = await waitForOutput(
|
|
786
|
-
/Type "yes" to confirm deletion:/
|
|
787
|
-
);
|
|
788
|
-
expect(foundPrompt).toBe(true);
|
|
789
|
-
} finally {
|
|
790
|
-
await env.stopAndCleanup();
|
|
791
|
-
}
|
|
792
|
-
});
|
|
793
|
-
|
|
794
|
-
it('should show loading state while checking resource', async () => {
|
|
795
|
-
const env = await createTestEnv();
|
|
796
|
-
const px = await env.start();
|
|
797
|
-
|
|
798
|
-
try {
|
|
799
|
-
// Stop server to simulate slow loading
|
|
800
|
-
env.server.stop();
|
|
801
|
-
|
|
802
|
-
const { waitForOutput } = await px([
|
|
803
|
-
'resources',
|
|
804
|
-
'upload',
|
|
805
|
-
'-d',
|
|
806
|
-
'any-file.txt',
|
|
807
|
-
]);
|
|
808
|
-
|
|
809
|
-
// Should show checking message
|
|
810
|
-
const foundChecking = await waitForOutput(/Checking resource\.\.\./);
|
|
811
|
-
expect(foundChecking).toBe(true);
|
|
812
|
-
|
|
813
|
-
// Should eventually show error when server is down
|
|
814
|
-
const foundError = await waitForOutput(/Error/);
|
|
815
|
-
expect(foundError).toBe(true);
|
|
816
|
-
} finally {
|
|
817
|
-
// Cleanup without stopping server (already stopped)
|
|
818
|
-
env.cleanup();
|
|
819
|
-
}
|
|
820
|
-
});
|
|
821
|
-
});
|
|
822
|
-
|
|
823
|
-
describe('resources clear command', () => {
|
|
824
|
-
it('should handle empty resources gracefully', async () => {
|
|
825
|
-
const env = await createTestEnv();
|
|
826
|
-
const px = await env.start();
|
|
827
|
-
|
|
828
|
-
try {
|
|
829
|
-
// TestDevServer starts with empty resources by default
|
|
830
|
-
const { waitForOutput } = await px(['resources', 'clear']);
|
|
831
|
-
const isOutputRendered = await waitForOutput(
|
|
832
|
-
/No resources to delete/,
|
|
833
|
-
20
|
|
834
|
-
);
|
|
835
|
-
|
|
836
|
-
expect(isOutputRendered).toBe(true);
|
|
837
|
-
} finally {
|
|
838
|
-
await env.stopAndCleanup();
|
|
839
|
-
}
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
it('should show warning for non-empty resources', async () => {
|
|
843
|
-
const env = await createTestEnv();
|
|
844
|
-
|
|
845
|
-
// Add some mock resources to the test server
|
|
846
|
-
env.server.addResource({
|
|
847
|
-
key: 'test.txt',
|
|
848
|
-
type: 'text',
|
|
849
|
-
size: 100,
|
|
850
|
-
lastModified: new Date().toISOString(),
|
|
851
|
-
});
|
|
852
|
-
env.server.addResource({
|
|
853
|
-
key: 'data/config.json',
|
|
854
|
-
type: 'text',
|
|
855
|
-
size: 200,
|
|
856
|
-
lastModified: new Date().toISOString(),
|
|
857
|
-
});
|
|
858
|
-
|
|
859
|
-
const px = await env.start();
|
|
860
|
-
|
|
861
|
-
try {
|
|
862
|
-
const { waitForOutput } = await px(['resources', 'clear']);
|
|
863
|
-
|
|
864
|
-
// Should show danger warning
|
|
865
|
-
const isWarningShown = await waitForOutput(
|
|
866
|
-
/DANGER: This will permanently delete ALL resources!/
|
|
867
|
-
);
|
|
868
|
-
expect(isWarningShown).toBe(true);
|
|
869
|
-
|
|
870
|
-
// Should show resource count (2 resources)
|
|
871
|
-
const isCountShown = await waitForOutput(
|
|
872
|
-
/This action will delete 2 resource\(s\)/
|
|
873
|
-
);
|
|
874
|
-
expect(isCountShown).toBe(true);
|
|
875
|
-
|
|
876
|
-
// Should show the selection prompt
|
|
877
|
-
const isPromptShown = await waitForOutput(
|
|
878
|
-
/Use arrow keys to select, Enter to confirm:/
|
|
879
|
-
);
|
|
880
|
-
expect(isPromptShown).toBe(true);
|
|
881
|
-
|
|
882
|
-
// Should show cancel option as selected by default
|
|
883
|
-
const isCancelSelected = await waitForOutput(
|
|
884
|
-
/▶ Cancel \(keep resources\)/
|
|
885
|
-
);
|
|
886
|
-
expect(isCancelSelected).toBe(true);
|
|
887
|
-
} finally {
|
|
888
|
-
await env.stopAndCleanup();
|
|
889
|
-
}
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
it('should navigate between options with arrow keys', async () => {
|
|
893
|
-
const env = await createTestEnv();
|
|
894
|
-
|
|
895
|
-
// Add a mock resource so we get the confirmation prompt
|
|
896
|
-
env.server.addResource({
|
|
897
|
-
key: 'test.txt',
|
|
898
|
-
type: 'text',
|
|
899
|
-
size: 100,
|
|
900
|
-
lastModified: new Date().toISOString(),
|
|
901
|
-
});
|
|
902
|
-
|
|
903
|
-
const px = await env.start();
|
|
904
|
-
|
|
905
|
-
try {
|
|
906
|
-
const { waitForOutput, instance } = await px(['resources', 'clear']);
|
|
907
|
-
|
|
908
|
-
// Wait for the prompt to appear
|
|
909
|
-
await waitForOutput(/Use arrow keys to select, Enter to confirm:/);
|
|
910
|
-
|
|
911
|
-
// Verify cancel is selected by default
|
|
912
|
-
const isCancelSelected = await waitForOutput(
|
|
913
|
-
/▶ Cancel \(keep resources\)/
|
|
914
|
-
);
|
|
915
|
-
expect(isCancelSelected).toBe(true);
|
|
916
|
-
|
|
917
|
-
// Press down arrow
|
|
918
|
-
instance.stdin.write('\u001B[B');
|
|
919
|
-
|
|
920
|
-
// Should show delete option as selected
|
|
921
|
-
const isDeleteSelected = await waitForOutput(
|
|
922
|
-
/▶ Delete all resources/,
|
|
923
|
-
30
|
|
924
|
-
);
|
|
925
|
-
expect(isDeleteSelected).toBe(true);
|
|
926
|
-
|
|
927
|
-
// Press up arrow to go back
|
|
928
|
-
instance.stdin.write('\u001B[A');
|
|
929
|
-
|
|
930
|
-
// Should show cancel option as selected again
|
|
931
|
-
const isCancelSelectedAgain = await waitForOutput(
|
|
932
|
-
/▶ Cancel \(keep resources\)/,
|
|
933
|
-
30
|
|
934
|
-
);
|
|
935
|
-
expect(isCancelSelectedAgain).toBe(true);
|
|
936
|
-
} finally {
|
|
937
|
-
await env.stopAndCleanup();
|
|
938
|
-
}
|
|
939
|
-
});
|
|
940
|
-
});
|
|
941
|
-
|
|
942
|
-
describe('resources types command', () => {
|
|
943
|
-
it('should generate resource types successfully', async () => {
|
|
944
|
-
const env = await createTestEnv();
|
|
945
|
-
const px = await env.start();
|
|
946
|
-
|
|
947
|
-
try {
|
|
948
|
-
const { waitForOutput } = await px(['resources', 'types']);
|
|
949
|
-
|
|
950
|
-
// Test user-visible output
|
|
951
|
-
const foundSuccess = await waitForOutput(/Generated resource types at/i);
|
|
952
|
-
expect(foundSuccess).toBe(true);
|
|
953
|
-
|
|
954
|
-
// Verify the types file was actually created
|
|
955
|
-
const typesPath = path.join(env.projectRootDir, 'resources.d.ts');
|
|
956
|
-
expect(fs.existsSync(typesPath)).toBe(true);
|
|
957
|
-
|
|
958
|
-
// Verify basic content of the types file
|
|
959
|
-
const typesContent = fs.readFileSync(typesPath, 'utf-8');
|
|
960
|
-
expect(typesContent).toContain("declare module '@positronic/core'");
|
|
961
|
-
expect(typesContent).toContain('interface Resources');
|
|
962
|
-
} finally {
|
|
963
|
-
await env.stopAndCleanup();
|
|
964
|
-
}
|
|
965
|
-
});
|
|
966
|
-
|
|
967
|
-
it('should handle type generation errors gracefully', async () => {
|
|
968
|
-
const env = await createTestEnv();
|
|
969
|
-
const px = await env.start();
|
|
970
|
-
|
|
971
|
-
try {
|
|
972
|
-
// Make the project directory read-only to cause an error
|
|
973
|
-
const typesPath = path.join(env.projectRootDir, 'resources.d.ts');
|
|
974
|
-
|
|
975
|
-
// Create a directory with the same name to cause a write error
|
|
976
|
-
fs.mkdirSync(typesPath);
|
|
977
|
-
|
|
978
|
-
const { waitForOutput } = await px(['resources', 'types']);
|
|
979
|
-
|
|
980
|
-
// Should show error message
|
|
981
|
-
const foundError = await waitForOutput(/Type Generation Failed/i);
|
|
982
|
-
expect(foundError).toBe(true);
|
|
983
|
-
|
|
984
|
-
// Should show some error details
|
|
985
|
-
const foundDetails = await waitForOutput(/EISDIR|directory/i);
|
|
986
|
-
expect(foundDetails).toBe(true);
|
|
987
|
-
} finally {
|
|
988
|
-
await env.stopAndCleanup();
|
|
989
|
-
}
|
|
990
|
-
});
|
|
991
|
-
});
|
|
992
|
-
|
|
993
|
-
describe('resources upload command', () => {
|
|
994
|
-
// Testing the upload command's file validation behavior
|
|
995
|
-
// The actual upload requires presigned URLs which aren't available in test environment
|
|
996
|
-
|
|
997
|
-
it('should handle non-existent file error', async () => {
|
|
998
|
-
const env = await createTestEnv();
|
|
999
|
-
const px = await env.start();
|
|
1000
|
-
|
|
1001
|
-
try {
|
|
1002
|
-
const nonExistentPath = path.join(env.projectRootDir, 'does-not-exist.txt');
|
|
1003
|
-
|
|
1004
|
-
const { waitForOutput } = await px(['resources', 'upload', nonExistentPath]);
|
|
1005
|
-
|
|
1006
|
-
// Should show error immediately since file check happens first
|
|
1007
|
-
const foundError = await waitForOutput(/File Not Found/i);
|
|
1008
|
-
expect(foundError).toBe(true);
|
|
1009
|
-
} finally {
|
|
1010
|
-
await env.stopAndCleanup();
|
|
1011
|
-
}
|
|
1012
|
-
});
|
|
1013
|
-
|
|
1014
|
-
it('should handle directory path error', async () => {
|
|
1015
|
-
const env = await createTestEnv();
|
|
1016
|
-
const px = await env.start();
|
|
1017
|
-
|
|
1018
|
-
try {
|
|
1019
|
-
// Try to upload a directory instead of a file
|
|
1020
|
-
const dirPath = path.join(env.projectRootDir, 'resources');
|
|
1021
|
-
|
|
1022
|
-
const { waitForOutput } = await px(['resources', 'upload', dirPath]);
|
|
1023
|
-
|
|
1024
|
-
// Should show error
|
|
1025
|
-
const foundError = await waitForOutput(/Invalid Path/i);
|
|
1026
|
-
expect(foundError).toBe(true);
|
|
1027
|
-
} finally {
|
|
1028
|
-
await env.stopAndCleanup();
|
|
1029
|
-
}
|
|
1030
|
-
});
|
|
1031
|
-
|
|
1032
|
-
it('should validate files before attempting upload', async () => {
|
|
1033
|
-
const env = await createTestEnv();
|
|
1034
|
-
const px = await env.start();
|
|
1035
|
-
|
|
1036
|
-
try {
|
|
1037
|
-
// Create a test file
|
|
1038
|
-
const testFilePath = path.join(env.projectRootDir, 'test.txt');
|
|
1039
|
-
fs.writeFileSync(testFilePath, 'Hello, world!');
|
|
1040
|
-
|
|
1041
|
-
const { waitForOutput } = await px(['resources', 'upload', testFilePath]);
|
|
1042
|
-
|
|
1043
|
-
// The component will validate the file and attempt upload
|
|
1044
|
-
// Without mocked presigned URLs, it will show an error about the API
|
|
1045
|
-
const foundOutput = await waitForOutput(/Uploading test\.txt|Failed|Error/i);
|
|
1046
|
-
expect(foundOutput).toBe(true);
|
|
1047
|
-
} finally {
|
|
1048
|
-
await env.stopAndCleanup();
|
|
1049
|
-
}
|
|
1050
|
-
});
|
|
1051
|
-
});
|
|
1052
|
-
});
|