meta.spec.ts 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import { describe, expect, it } from 'vitest'
  2. import { WorkflowError } from '@deepseek-ai/dsh-workflow'
  3. import { validateMeta } from '../src/meta.ts'
  4. /** Assert a META_INVALID throw whose message matches every given fragment. */
  5. function expectInvalid(value: unknown, ...fragments: string[]): void {
  6. let thrown: unknown
  7. try {
  8. validateMeta(value)
  9. } catch (error: unknown) {
  10. thrown = error
  11. }
  12. expect(thrown).toBeInstanceOf(WorkflowError)
  13. expect((thrown as WorkflowError).code).toBe('META_INVALID')
  14. for (const fragment of fragments) {
  15. expect((thrown as WorkflowError).message).toContain(fragment)
  16. }
  17. }
  18. describe('validateMeta', () => {
  19. it('accepts a minimal meta and returns a normalized copy (no aliasing of the input)', () => {
  20. const input = { name: 'audit', description: 'audit the repo' }
  21. const meta = validateMeta(input)
  22. expect(meta).toEqual({ name: 'audit', description: 'audit the repo' })
  23. expect(meta).not.toBe(input)
  24. input.name = 'mutated'
  25. expect(meta.name).toBe('audit')
  26. })
  27. it('accepts the full shape and rebuilds phases entry by entry', () => {
  28. const meta = validateMeta({
  29. name: 'migrate',
  30. description: 'migrate call sites',
  31. whenToUse: 'large mechanical sweeps',
  32. phases: [
  33. { title: 'Discover', provider: 'openai' },
  34. { title: 'Transform', detail: 'one agent per file', model: 'deepseek-v4-pro' },
  35. ],
  36. })
  37. expect(meta).toEqual({
  38. name: 'migrate',
  39. description: 'migrate call sites',
  40. whenToUse: 'large mechanical sweeps',
  41. phases: [
  42. { title: 'Discover', provider: 'openai' },
  43. { title: 'Transform', detail: 'one agent per file', model: 'deepseek-v4-pro' },
  44. ],
  45. })
  46. })
  47. it('rejects non-object values loud', () => {
  48. expectInvalid(undefined, 'meta must be an object')
  49. expectInvalid('a string', 'meta must be an object')
  50. expectInvalid(null, 'meta must be an object')
  51. expectInvalid([{ name: 'x', description: 'd' }], 'meta must be an object')
  52. })
  53. it('rejects unknown fields by name (accepted-then-ignored is banned)', () => {
  54. expectInvalid({ name: 'x', description: 'd', color: 'red' }, 'meta.color is not a recognized field')
  55. })
  56. it('rejects missing or mistyped name/description/whenToUse', () => {
  57. expectInvalid({ description: 'd' }, 'meta.name must be a non-empty string')
  58. expectInvalid({ name: '', description: 'd' }, 'meta.name must be a non-empty string')
  59. expectInvalid({ name: 'x' }, 'meta.description must be a non-empty string')
  60. expectInvalid({ name: 'x', description: 42 }, 'meta.description must be a non-empty string')
  61. expectInvalid({ name: 'x', description: 'd', whenToUse: 3 }, 'meta.whenToUse must be a string')
  62. })
  63. it('rejects malformed phases, entry by entry', () => {
  64. expectInvalid({ name: 'x', description: 'd', phases: 'Scan' }, 'meta.phases must be an array')
  65. expectInvalid({ name: 'x', description: 'd', phases: ['Scan'] }, 'meta.phases[0] must be an object')
  66. expectInvalid({ name: 'x', description: 'd', phases: [{ title: '' }] }, 'meta.phases[0].title must be a non-empty string')
  67. expectInvalid({ name: 'x', description: 'd', phases: [{ title: 'Scan', order: 1 }] }, 'meta.phases[0].order is not a recognized field')
  68. expectInvalid({ name: 'x', description: 'd', phases: [{ title: 'Scan', detail: 9 }] }, 'meta.phases[0].detail must be a string')
  69. expectInvalid({ name: 'x', description: 'd', phases: [{ title: 'Scan', provider: 9 }] }, 'meta.phases[0].provider must be a string')
  70. expectInvalid({ name: 'x', description: 'd', phases: [{ title: 'Scan', model: 9 }] }, 'meta.phases[0].model must be a string')
  71. })
  72. it('names EVERY violation in one throw, not just the first', () => {
  73. expectInvalid(
  74. { description: 7, extra: true, phases: [{ title: 'Scan' }, 'bad'] },
  75. 'meta.extra is not a recognized field',
  76. 'meta.name must be a non-empty string',
  77. 'meta.description must be a non-empty string',
  78. 'meta.phases[1] must be an object',
  79. )
  80. })
  81. })