@principles/pd-cli 1.106.0 → 1.107.0
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/package.json
CHANGED
|
@@ -281,4 +281,157 @@ funnels:
|
|
|
281
281
|
expect(typeof parsed).toBe('object');
|
|
282
282
|
});
|
|
283
283
|
});
|
|
284
|
+
|
|
285
|
+
// ── Boundary Condition Tests ────────────────────────────────────────────────
|
|
286
|
+
|
|
287
|
+
describe('resolveRuntimeWithOverrides', () => {
|
|
288
|
+
it('CLI overrides take precedence over config values', async () => {
|
|
289
|
+
writeConfigYaml(tmpDir, makeValidConfigYaml({ provider: 'lmstudio', model: 'local-model' }));
|
|
290
|
+
|
|
291
|
+
const { resolveRuntimeWithOverrides } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
292
|
+
const resolved = resolveRuntimeWithOverrides(tmpDir, {
|
|
293
|
+
provider: 'override-provider',
|
|
294
|
+
model: 'override-model',
|
|
295
|
+
}, () => 'test-key');
|
|
296
|
+
|
|
297
|
+
expect(resolved.mergedConfig).not.toBeNull();
|
|
298
|
+
if (resolved.mergedConfig) {
|
|
299
|
+
expect(resolved.mergedConfig.provider).toBe('override-provider');
|
|
300
|
+
expect(resolved.mergedConfig.model).toBe('override-model');
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('returns mergedConfig=null when base config is malformed', async () => {
|
|
305
|
+
const pdDir = path.join(tmpDir, '.pd');
|
|
306
|
+
fs.mkdirSync(pdDir, { recursive: true });
|
|
307
|
+
fs.writeFileSync(path.join(pdDir, 'config.yaml'), 'version: [invalid', 'utf8');
|
|
308
|
+
|
|
309
|
+
const { resolveRuntimeWithOverrides } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
310
|
+
const resolved = resolveRuntimeWithOverrides(tmpDir, {
|
|
311
|
+
provider: 'override',
|
|
312
|
+
}, () => 'test-key');
|
|
313
|
+
|
|
314
|
+
expect(resolved.mergedConfig).toBeNull();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('preserves config values when overrides are undefined', async () => {
|
|
318
|
+
writeConfigYaml(tmpDir, makeValidConfigYaml({ provider: 'lmstudio', model: 'local-model' }));
|
|
319
|
+
|
|
320
|
+
const { resolveRuntimeWithOverrides } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
321
|
+
const resolved = resolveRuntimeWithOverrides(tmpDir, {
|
|
322
|
+
provider: undefined,
|
|
323
|
+
model: undefined,
|
|
324
|
+
}, () => 'test-key');
|
|
325
|
+
|
|
326
|
+
expect(resolved.mergedConfig).not.toBeNull();
|
|
327
|
+
if (resolved.mergedConfig) {
|
|
328
|
+
expect(resolved.mergedConfig.provider).toBe('lmstudio');
|
|
329
|
+
expect(resolved.mergedConfig.model).toBe('local-model');
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('merges numeric overrides correctly', async () => {
|
|
334
|
+
writeConfigYaml(tmpDir, makeValidConfigYaml());
|
|
335
|
+
|
|
336
|
+
const { resolveRuntimeWithOverrides } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
337
|
+
const resolved = resolveRuntimeWithOverrides(tmpDir, {
|
|
338
|
+
maxRetries: 5,
|
|
339
|
+
timeoutMs: 60000,
|
|
340
|
+
}, () => 'test-key');
|
|
341
|
+
|
|
342
|
+
expect(resolved.mergedConfig).not.toBeNull();
|
|
343
|
+
if (resolved.mergedConfig) {
|
|
344
|
+
expect(resolved.mergedConfig.maxRetries).toBe(5);
|
|
345
|
+
expect(resolved.mergedConfig.timeoutMs).toBe(60000);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
describe('edge cases and error handling', () => {
|
|
351
|
+
it('handles empty workspace directory gracefully', async () => {
|
|
352
|
+
// No .pd directory at all
|
|
353
|
+
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
354
|
+
const resolved = resolveRuntimeFromPdConfig(tmpDir, () => 'test-key');
|
|
355
|
+
|
|
356
|
+
// Missing config should return defaults (ok=true with source='defaults')
|
|
357
|
+
expect(resolved.configLoadResult.ok).toBe(true);
|
|
358
|
+
expect(resolved.configSource).toBe('.pd/config.yaml');
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('handles config with missing runtimeProfiles section', async () => {
|
|
362
|
+
writeConfigYaml(tmpDir, {
|
|
363
|
+
version: 1,
|
|
364
|
+
features: {},
|
|
365
|
+
// Missing runtimeProfiles
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
369
|
+
const { isRuntimeConfigError } = await import('@principles/core/runtime-v2');
|
|
370
|
+
const resolved = resolveRuntimeFromPdConfig(tmpDir, () => 'test-key');
|
|
371
|
+
|
|
372
|
+
// Missing runtimeProfiles should either load with defaults or produce
|
|
373
|
+
// a RuntimeConfigError — never silently succeed with wrong config.
|
|
374
|
+
if (isRuntimeConfigError(resolved.result)) {
|
|
375
|
+
expect(resolved.result.reason).toBeTruthy();
|
|
376
|
+
expect(resolved.result.nextAction).toBeTruthy();
|
|
377
|
+
} else {
|
|
378
|
+
// If defaults were returned, configLoadResult.ok should be true
|
|
379
|
+
expect(resolved.configLoadResult.ok).toBe(true);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('handles config with invalid runtime profile type', async () => {
|
|
384
|
+
writeConfigYaml(tmpDir, {
|
|
385
|
+
version: 1,
|
|
386
|
+
runtimeProfiles: {
|
|
387
|
+
'bad-profile': {
|
|
388
|
+
type: 'invalid-type', // Invalid type
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
internalAgents: {
|
|
392
|
+
defaultRuntime: 'bad-profile',
|
|
393
|
+
agents: { diagnostician: { enabled: true, runtimeProfile: 'bad-profile' } },
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
398
|
+
const { isRuntimeConfigError } = await import('@principles/core/runtime-v2');
|
|
399
|
+
const resolved = resolveRuntimeFromPdConfig(tmpDir, () => 'test-key');
|
|
400
|
+
|
|
401
|
+
// Invalid type should be caught — verify external contract, not internal state
|
|
402
|
+
expect(isRuntimeConfigError(resolved.result)).toBe(true);
|
|
403
|
+
if (isRuntimeConfigError(resolved.result)) {
|
|
404
|
+
expect(resolved.result.reason).toBeTruthy();
|
|
405
|
+
expect(resolved.result.nextAction).toBeTruthy();
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it('handles env var returning undefined for apiKeyEnv', async () => {
|
|
410
|
+
writeConfigYaml(tmpDir, makeValidConfigYaml());
|
|
411
|
+
|
|
412
|
+
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
413
|
+
const resolved = resolveRuntimeFromPdConfig(tmpDir, () => undefined);
|
|
414
|
+
|
|
415
|
+
// Should handle undefined env var gracefully
|
|
416
|
+
expect(resolved.result).toBeDefined();
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('handles multiple legacy files detected', async () => {
|
|
420
|
+
writeConfigYaml(tmpDir, makeValidConfigYaml());
|
|
421
|
+
|
|
422
|
+
// Create multiple legacy files
|
|
423
|
+
const stateDir = path.join(tmpDir, '.state');
|
|
424
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
425
|
+
fs.writeFileSync(path.join(stateDir, 'workflows.yaml'), 'version: 1', 'utf8');
|
|
426
|
+
|
|
427
|
+
const ffDir = path.join(tmpDir, '.pd');
|
|
428
|
+
fs.writeFileSync(path.join(ffDir, 'feature-flags.yaml'), 'flags: []', 'utf8');
|
|
429
|
+
|
|
430
|
+
const { resolveRuntimeFromPdConfig } = await import('../../src/services/resolve-runtime-from-pd-config.js');
|
|
431
|
+
const resolved = resolveRuntimeFromPdConfig(tmpDir, () => 'test-key');
|
|
432
|
+
|
|
433
|
+
expect(resolved.legacyWarnings.length).toBeGreaterThan(0);
|
|
434
|
+
expect(resolved.configLoadResult.legacyFilesDetected.length).toBeGreaterThanOrEqual(2);
|
|
435
|
+
});
|
|
436
|
+
});
|
|
284
437
|
});
|