happyskills 1.16.3 → 1.18.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.
@@ -334,3 +334,133 @@ describe('validate_skill_json — systemDependencies', () => {
334
334
  assert.strictEqual(check.severity, 'pass')
335
335
  })
336
336
  })
337
+
338
+ describe('validate_skill_json — config schema', () => {
339
+ it('errors when config is an array', async () => {
340
+ write_json(tmp, { name: 'my-skill', version: '1.0.0', config: [] })
341
+ const [err, data] = await validate_skill_json(tmp)
342
+ assert.ifError(err)
343
+ const check = data.results.find(r => r.field === 'config' && r.rule === 'type')
344
+ assert.strictEqual(check.severity, 'error')
345
+ })
346
+
347
+ it('errors when a config field has an unknown type', async () => {
348
+ write_json(tmp, {
349
+ name: 'my-skill', version: '1.0.0',
350
+ config: { channel: { type: 'colour', default: '#general' } }
351
+ })
352
+ const [err, data] = await validate_skill_json(tmp)
353
+ assert.ifError(err)
354
+ const check = data.results.find(r => r.field === 'config' && r.rule === 'type_value')
355
+ assert.strictEqual(check.severity, 'error')
356
+ assert.ok(check.message.includes('colour'))
357
+ })
358
+
359
+ it('errors when a config field omits type', async () => {
360
+ write_json(tmp, {
361
+ name: 'my-skill', version: '1.0.0',
362
+ config: { channel: { default: '#general' } }
363
+ })
364
+ const [err, data] = await validate_skill_json(tmp)
365
+ assert.ifError(err)
366
+ const check = data.results.find(r => r.field === 'config' && r.rule === 'type_value')
367
+ assert.strictEqual(check.severity, 'error')
368
+ })
369
+
370
+ it('errors when a config field required flag is non-boolean', async () => {
371
+ write_json(tmp, {
372
+ name: 'my-skill', version: '1.0.0',
373
+ config: { channel: { type: 'string', required: 'yes' } }
374
+ })
375
+ const [err, data] = await validate_skill_json(tmp)
376
+ assert.ifError(err)
377
+ const check = data.results.find(r => r.field === 'config' && r.rule === 'required_flag')
378
+ assert.strictEqual(check.severity, 'error')
379
+ })
380
+ })
381
+
382
+ describe('validate_skill_json — env schema', () => {
383
+ it('errors when env is an array', async () => {
384
+ write_json(tmp, { name: 'my-skill', version: '1.0.0', env: [] })
385
+ const [err, data] = await validate_skill_json(tmp)
386
+ assert.ifError(err)
387
+ const check = data.results.find(r => r.field === 'env' && r.rule === 'type')
388
+ assert.strictEqual(check.severity, 'error')
389
+ })
390
+
391
+ it('errors when required is non-boolean', async () => {
392
+ write_json(tmp, {
393
+ name: 'my-skill', version: '1.0.0',
394
+ env: { SLACK_BOT_TOKEN: { required: 'true', secret: true } }
395
+ })
396
+ const [err, data] = await validate_skill_json(tmp)
397
+ assert.ifError(err)
398
+ const check = data.results.find(r => r.field === 'env' && r.rule === 'required_flag')
399
+ assert.strictEqual(check.severity, 'error')
400
+ })
401
+
402
+ it('errors when secret is non-boolean', async () => {
403
+ write_json(tmp, {
404
+ name: 'my-skill', version: '1.0.0',
405
+ env: { SLACK_BOT_TOKEN: { required: true, secret: 'yes' } }
406
+ })
407
+ const [err, data] = await validate_skill_json(tmp)
408
+ assert.ifError(err)
409
+ const check = data.results.find(r => r.field === 'env' && r.rule === 'secret_flag')
410
+ assert.strictEqual(check.severity, 'error')
411
+ })
412
+
413
+ it('warns when a secret env var carries an inline default', async () => {
414
+ write_json(tmp, {
415
+ name: 'my-skill', version: '1.0.0',
416
+ env: { SLACK_BOT_TOKEN: { required: true, secret: true, default: 'xoxb-hardcoded' } }
417
+ })
418
+ const [err, data] = await validate_skill_json(tmp)
419
+ assert.ifError(err)
420
+ const check = data.results.find(r => r.field === 'env' && r.rule === 'secret_default')
421
+ assert.strictEqual(check.severity, 'warning')
422
+ })
423
+
424
+ it('errors when a validate regex does not compile', async () => {
425
+ write_json(tmp, {
426
+ name: 'my-skill', version: '1.0.0',
427
+ env: { DEPLOY_TIMEOUT: { type: 'integer', validate: '([0-9' } }
428
+ })
429
+ const [err, data] = await validate_skill_json(tmp)
430
+ assert.ifError(err)
431
+ const check = data.results.find(r => r.field === 'env' && r.rule === 'validate_regex')
432
+ assert.strictEqual(check.severity, 'error')
433
+ })
434
+
435
+ it('errors when two env vars differ only by case', async () => {
436
+ write_json(tmp, {
437
+ name: 'my-skill', version: '1.0.0',
438
+ env: { TOKEN: { required: true }, token: { required: false } }
439
+ })
440
+ const [err, data] = await validate_skill_json(tmp)
441
+ assert.ifError(err)
442
+ const check = data.results.find(r => r.field === 'env' && r.rule === 'case_collision')
443
+ assert.strictEqual(check.severity, 'error')
444
+ })
445
+ })
446
+
447
+ describe('validate_skill_json — well-formed config + env', () => {
448
+ it('emits no error or warning results for a well-formed config + env block', async () => {
449
+ write_json(tmp, {
450
+ name: 'slack-notify', version: '1.0.0',
451
+ config: {
452
+ channel: { type: 'string', default: '#general', description: 'Channel to post to' },
453
+ max_chars: { type: 'integer', default: 500, description: 'Truncate longer messages' }
454
+ },
455
+ env: {
456
+ SLACK_BOT_TOKEN: { required: true, secret: true, description: 'Slack bot token' },
457
+ DEPLOY_TIMEOUT: { required: false, secret: false, type: 'integer', default: 300, validate: '^[0-9]+$' }
458
+ }
459
+ })
460
+ const [err, data] = await validate_skill_json(tmp)
461
+ assert.ifError(err)
462
+ const config_env_problems = data.results.filter(r =>
463
+ (r.field === 'config' || r.field === 'env') && (r.severity === 'error' || r.severity === 'warning'))
464
+ assert.deepStrictEqual(config_env_problems, [])
465
+ })
466
+ })