credentials-controller.host.spec.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import { describe, expect, it } from 'vitest'
  2. import { Context } from '@deepseek-ai/cordis'
  3. import type { CredentialInfo } from '@deepseek-ai/dsh-credentials/types'
  4. import { TypertRemoteFailure, remoteMethods } from '@deepseek-ai/dsh-typert-protocol'
  5. import CredentialsController from '../src/credentials.ts'
  6. import { MemoryCredentials } from '../../../credentials/credentials/tests/memory.ts'
  7. /** A store whose `describe` carries more than the view declares, as a foreign provider might. */
  8. class LeakyCredentials extends MemoryCredentials {
  9. override describe(): Promise<CredentialInfo> {
  10. return Promise.resolve(
  11. { configured: true, source: 'memory', writable: true, value: 'sk-leaked' } as CredentialInfo,
  12. )
  13. }
  14. }
  15. /** A store whose write rejects with a bare string, the way some client libraries do. */
  16. class LiteralRejectingCredentials extends MemoryCredentials {
  17. override async set(): Promise<void> {
  18. throw 'the store refused'
  19. }
  20. }
  21. /** A store whose provider-owned policy rejects an otherwise valid write. */
  22. class RejectingCredentials extends MemoryCredentials {
  23. override set(): Promise<void> {
  24. return Promise.reject(new Error('a read-only source shadows this reference'))
  25. }
  26. }
  27. async function boot(
  28. seed: Record<string, string> = {},
  29. provider: typeof MemoryCredentials = MemoryCredentials,
  30. ): Promise<CredentialsController> {
  31. const ctx = new Context()
  32. await ctx.plugin(provider, seed)
  33. await ctx.plugin(CredentialsController)
  34. return ctx.credentialsController
  35. }
  36. describe('the credentials Remote namespace a configuration surface calls', () => {
  37. it('publishes the credentials namespace from its own service key', async () => {
  38. const controller = await boot()
  39. const binding = controller.typertRemote
  40. expect(binding.serviceKey).toBe('credentialsController')
  41. expect(binding.namespace).toBe('credentials')
  42. expect(remoteMethods(controller)).toEqual([
  43. { method: 'describe', invocation: { kind: 'direct' } },
  44. { method: 'set', invocation: { kind: 'direct' } },
  45. { method: 'unset', invocation: { kind: 'direct' } },
  46. ])
  47. })
  48. it('reports the actionable configuration error while no credential provider is mounted', async () => {
  49. const ctx = new Context()
  50. await ctx.plugin(CredentialsController)
  51. for (const call of [
  52. () => ctx.credentialsController.describe(['DEEPSEEK_API_KEY']),
  53. () => ctx.credentialsController.set('DEEPSEEK_API_KEY', 'sk-live'),
  54. () => ctx.credentialsController.unset('DEEPSEEK_API_KEY'),
  55. ]) {
  56. const failure = await call().catch((error: unknown) => error)
  57. expect(failure).toBeInstanceOf(TypertRemoteFailure)
  58. expect((failure as TypertRemoteFailure).failure).toEqual({
  59. code: 'internal',
  60. message: 'credentials service is absent: this deployment does not mount a credential provider (e.g. @deepseek-ai/dsh-credentials-local) in its composition',
  61. details: {},
  62. })
  63. }
  64. })
  65. it('describes a batch of references as one map, values excluded', async () => {
  66. const controller = await boot({ DEEPSEEK_API_KEY: 'sk-seeded' })
  67. const described = await controller.describe(['DEEPSEEK_API_KEY', 'OPENAI_API_KEY'])
  68. expect(described).toEqual({
  69. DEEPSEEK_API_KEY: { configured: true, source: 'memory', writable: true },
  70. OPENAI_API_KEY: { configured: false, writable: true },
  71. })
  72. expect(JSON.stringify(described)).not.toContain('sk-seeded')
  73. })
  74. it('reports an invalid reference as bad-request', async () => {
  75. const controller = await boot()
  76. for (const call of [
  77. () => controller.describe(['DEEPSEEK_API_KEY', 'not a var']),
  78. () => controller.set('not a var', 'sk-live'),
  79. () => controller.unset('not a var'),
  80. ]) {
  81. const failure = await call().catch((error: unknown) => error)
  82. expect(failure).toBeInstanceOf(TypertRemoteFailure)
  83. expect((failure as TypertRemoteFailure).failure).toMatchObject({ code: 'bad-request' })
  84. }
  85. })
  86. it('answers the largest batch it accepts and reports one reference more as bad-request', async () => {
  87. const controller = await boot()
  88. const accepted = Array.from({ length: 64 }, (_unused, index) => `REF_${String(index)}`)
  89. expect(Object.keys(await controller.describe(accepted))).toHaveLength(64)
  90. const failure = await controller.describe([...accepted, 'REF_64']).catch((error: unknown) => error)
  91. expect((failure as TypertRemoteFailure).failure).toMatchObject({ code: 'bad-request' })
  92. })
  93. it('answers only the fields the view declares, whatever a provider returns', async () => {
  94. const controller = await boot({}, LeakyCredentials)
  95. const described = await controller.describe(['DEEPSEEK_API_KEY'])
  96. expect(described.DEEPSEEK_API_KEY).toEqual({ configured: true, source: 'memory', writable: true })
  97. expect(JSON.stringify(described)).not.toContain('sk-leaked')
  98. })
  99. it('stores and removes through the same references the batch describes', async () => {
  100. const controller = await boot()
  101. await controller.set('DEEPSEEK_API_KEY', 'sk-live')
  102. expect(await controller.describe(['DEEPSEEK_API_KEY']))
  103. .toEqual({ DEEPSEEK_API_KEY: { configured: true, source: 'memory', writable: true } })
  104. await controller.unset('DEEPSEEK_API_KEY')
  105. expect(await controller.describe(['DEEPSEEK_API_KEY']))
  106. .toEqual({ DEEPSEEK_API_KEY: { configured: false, writable: true } })
  107. })
  108. it('reports a refused write as credential-rejected naming only the reference', async () => {
  109. const controller = await boot({}, RejectingCredentials)
  110. const failure = await controller.set('DEEPSEEK_API_KEY', 'sk-live').catch((error: unknown) => error)
  111. expect(failure).toBeInstanceOf(TypertRemoteFailure)
  112. const { code, message, details } = (failure as TypertRemoteFailure).failure
  113. expect(code).toBe('credential-rejected')
  114. expect(message).toContain('read-only source')
  115. expect(details).toEqual({ ref: 'DEEPSEEK_API_KEY' })
  116. })
  117. it('reports an empty value as bad-request', async () => {
  118. const controller = await boot()
  119. const failure = await controller.set('DEEPSEEK_API_KEY', '').catch((error: unknown) => error)
  120. expect((failure as TypertRemoteFailure).failure).toMatchObject({ code: 'bad-request' })
  121. })
  122. it('stringifies a refusal that is not an Error', async () => {
  123. const controller = await boot({}, LiteralRejectingCredentials)
  124. const failure = await controller.set('DEEPSEEK_API_KEY', 'sk-live').catch((error: unknown) => error)
  125. expect((failure as TypertRemoteFailure).failure.message).toBe('the store refused')
  126. })
  127. })