verify-cordis-config.spec.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /**
  2. * The verify-cordis-config metadata contract: `disabled` is the one entry
  3. * metadata field whose `!!js` expression the Loader interpolates; every other
  4. * metadata field must stay static, and a disabled expression must parse.
  5. */
  6. import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
  7. import { tmpdir } from 'node:os'
  8. import { join } from 'node:path'
  9. import { describe, expect, it } from 'vitest'
  10. import {
  11. bundleManifestPaths,
  12. bundlePluginDependencyErrors,
  13. metadataExpressionErrors,
  14. packageTestFixtureDependencyErrors,
  15. packageTestPluginDependencyErrors,
  16. } from './verify-cordis-config.ts'
  17. describe('verify-cordis-config metadata expressions', () => {
  18. it('accepts a disabled !!js expression', () => {
  19. const problems = metadataExpressionErrors(
  20. { id: 'tool-bash', name: '@deepseek-ai/dsh-tool-bash', disabled: { __jsExpr: "process.platform === 'win32'" } },
  21. '[0]',
  22. )
  23. expect(problems).toEqual([])
  24. })
  25. it('rejects an expression in a static metadata field', () => {
  26. const problems = metadataExpressionErrors({ id: { __jsExpr: 'process.platform' }, name: 'pkg' }, '[0]')
  27. expect(problems).toContain('[0].id: !!js is not interpolated here')
  28. })
  29. it('rejects an expression nested below disabled (only the field itself interpolates)', () => {
  30. const problems = metadataExpressionErrors(
  31. { id: 'tool-bash', name: 'pkg', disabled: { when: { __jsExpr: 'process.platform' } } },
  32. '[0]',
  33. )
  34. expect(problems).toContain('[0].disabled.when: !!js is not interpolated here')
  35. })
  36. it('rejects a disabled expression that does not parse (the loader would fail the boot)', () => {
  37. const problems = metadataExpressionErrors(
  38. { id: 'tool-bash', name: 'pkg', disabled: { __jsExpr: 'process.platform ===' } },
  39. '[0]',
  40. )
  41. expect(problems.some(problem => problem.includes('[0].disabled: disabled expression does not parse'))).toBe(true)
  42. })
  43. })
  44. describe('workspace Bundle discovery and product dependency closures', () => {
  45. it('discovers a Bundle outside packages/bundle from its manifest declaration', () => {
  46. const fixture = mkdtempSync(join(tmpdir(), 'dsh-bundle-discovery-'))
  47. try {
  48. const bundleDir = join(fixture, 'packages/subagent/example')
  49. const plainDir = join(fixture, 'packages/bundle/plain')
  50. mkdirSync(bundleDir, { recursive: true })
  51. mkdirSync(plainDir, { recursive: true })
  52. writeFileSync(join(bundleDir, 'package.json'), JSON.stringify({
  53. name: '@deepseek-ai/dsh-subagent-example',
  54. dsh: { bundle: { patch: './cordis.patch.yml' } },
  55. }))
  56. writeFileSync(join(plainDir, 'package.json'), JSON.stringify({
  57. name: '@deepseek-ai/dsh-plain',
  58. }))
  59. expect(bundleManifestPaths(fixture)).toEqual([
  60. 'packages/subagent/example/package.json',
  61. ])
  62. } finally {
  63. rmSync(fixture, { recursive: true, force: true })
  64. }
  65. })
  66. it('allows a Bundle to mount itself but rejects an undeclared plugin package', () => {
  67. const manifestPath = 'packages/subagent/example/package.json'
  68. const file = 'packages/subagent/example/cordis.patch.yml'
  69. const manifest = {
  70. name: '@deepseek-ai/dsh-subagent-example',
  71. dependencies: {},
  72. }
  73. const self = { file, name: '@deepseek-ai/dsh-subagent-example' }
  74. expect(bundlePluginDependencyErrors(manifestPath, manifest, [self])).toEqual([])
  75. expect(bundlePluginDependencyErrors(manifestPath, manifest, [
  76. self,
  77. { file, name: '@deepseek-ai/dsh-missing-plugin' },
  78. ])).toEqual([
  79. `${file}: @deepseek-ai/dsh-missing-plugin must be declared in ${manifestPath} dependencies`,
  80. ])
  81. })
  82. })
  83. describe('package-owned Loader test dependency closures', () => {
  84. it('requires package test configs to declare each named plugin they load', () => {
  85. const manifestPath = 'packages/example/owner/package.json'
  86. const file = 'packages/example/owner/tests/fixtures/cordis.yml'
  87. const manifest = {
  88. name: '@deepseek-ai/dsh-owner',
  89. dependencies: {},
  90. devDependencies: {
  91. '@deepseek-ai/dsh-declared': 'workspace:^',
  92. },
  93. }
  94. expect(packageTestPluginDependencyErrors(manifestPath, manifest, [
  95. { file, name: '@deepseek-ai/dsh-owner' },
  96. { file, name: '@deepseek-ai/dsh-declared' },
  97. { file, name: '@deepseek-ai/dsh-missing' },
  98. ])).toEqual([
  99. `${file}: @deepseek-ai/dsh-missing must be declared in ${manifestPath} dependencies or devDependencies`,
  100. ])
  101. })
  102. it('requires executable package test fixtures to declare their bare imports', () => {
  103. const fixture = mkdtempSync(join(tmpdir(), 'dsh-package-test-entrypoint-'))
  104. try {
  105. const packageDir = join(fixture, 'packages/example/owner')
  106. const driverDir = join(packageDir, 'tests/fixtures/loader')
  107. mkdirSync(driverDir, { recursive: true })
  108. writeFileSync(join(packageDir, 'package.json'), JSON.stringify({
  109. name: '@deepseek-ai/dsh-owner',
  110. devDependencies: {
  111. '@deepseek-ai/dsh-declared': 'workspace:^',
  112. },
  113. }))
  114. writeFileSync(join(driverDir, 'driver.ts'), [
  115. "import '@deepseek-ai/dsh-owner'",
  116. "import '@deepseek-ai/dsh-declared'",
  117. "import '@deepseek-ai/dsh-missing'",
  118. ].join('\n'))
  119. writeFileSync(join(driverDir, 'cordis.yml'), '[]\n')
  120. writeFileSync(join(driverDir, 'fixture.mjs'), "import '@deepseek-ai/dsh-declared'\n")
  121. const unrelatedDir = join(packageDir, 'tests/fixtures/unrelated')
  122. mkdirSync(unrelatedDir, { recursive: true })
  123. writeFileSync(join(unrelatedDir, 'driver.ts'), "import '@deepseek-ai/dsh-unrelated'\n")
  124. expect(packageTestFixtureDependencyErrors(fixture)).toEqual([
  125. 'packages/example/owner/tests/fixtures/loader/driver.ts: '
  126. + '@deepseek-ai/dsh-missing must be declared in '
  127. + 'packages/example/owner/package.json dependencies or devDependencies',
  128. ])
  129. } finally {
  130. rmSync(fixture, { recursive: true, force: true })
  131. }
  132. })
  133. it('fails loud when package-owned Loader fixtures disappear from the scan', () => {
  134. const fixture = mkdtempSync(join(tmpdir(), 'dsh-empty-package-test-entrypoint-'))
  135. try {
  136. expect(packageTestFixtureDependencyErrors(fixture)).toEqual([
  137. 'package test fixture dependency scan found no package-owned Loader configs',
  138. ])
  139. } finally {
  140. rmSync(fixture, { recursive: true, force: true })
  141. }
  142. })
  143. })