plugin.spec.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import { afterEach, describe, expect, it } from 'vitest'
  2. import { Context } from '@deepseek-ai/cordis'
  3. import InvariantRegistry from '@deepseek-ai/dsh-invariants'
  4. import { getGlobalDispatcher } from 'undici'
  5. import { PROXY_ENV_NAMES } from '../src/policy.ts'
  6. import * as HttpProxy from '../src/index.ts'
  7. import * as HttpProxyInvariant from '../src/invariant.ts'
  8. const PROXY = 'http://127.0.0.1:7897'
  9. // `scripts/test-proxy-environment.ts` clears the machine's proxy variables before any suite runs,
  10. // so each test starts from nothing and only has to undo what `withEnv` set.
  11. afterEach(() => {
  12. for (const name of PROXY_ENV_NAMES) Reflect.deleteProperty(process.env, name)
  13. })
  14. /** The launcher normally provides a snapshot; without one the plugin reads the process environment. */
  15. function withEnv(values: Record<string, string>): void {
  16. for (const [name, value] of Object.entries(values)) process.env[name] = value
  17. }
  18. describe('http-proxy plugin', () => {
  19. it('installs the configured policy and restores the dispatcher on disposal', async () => {
  20. const before = getGlobalDispatcher()
  21. const ctx = new Context()
  22. const fiber = await ctx.plugin(HttpProxy, { httpProxy: PROXY })
  23. expect(HttpProxy.currentProxyPolicy()?.httpProxy).toBe(PROXY)
  24. expect(getGlobalDispatcher()).not.toBe(before)
  25. await fiber.dispose()
  26. expect(HttpProxy.currentProxyPolicy()).toBeUndefined()
  27. expect(getGlobalDispatcher()).toBe(before)
  28. })
  29. it('lets a real environment variable outrank the configured proxy', async () => {
  30. withEnv({ HTTP_PROXY: PROXY })
  31. const ctx = new Context()
  32. const fiber = await ctx.plugin(HttpProxy, { httpProxy: 'http://127.0.0.1:9' })
  33. try {
  34. expect(HttpProxy.currentProxyPolicy()?.httpProxy).toBe(PROXY)
  35. } finally {
  36. await fiber.dispose()
  37. }
  38. })
  39. it('installs nothing under mode off, even with a proxy in the environment', async () => {
  40. withEnv({ HTTP_PROXY: PROXY })
  41. const before = getGlobalDispatcher()
  42. const ctx = new Context()
  43. const fiber = await ctx.plugin(HttpProxy, { mode: 'off' })
  44. try {
  45. expect(HttpProxy.currentProxyPolicy()?.source).toBe('none')
  46. expect(getGlobalDispatcher()).toBe(before)
  47. } finally {
  48. await fiber.dispose()
  49. }
  50. })
  51. it('reports an unusable environment value and connects directly instead of failing the load', async () => {
  52. withEnv({ HTTP_PROXY: 'socks5://127.0.0.1:1080' })
  53. const before = getGlobalDispatcher()
  54. const ctx = new Context()
  55. const fiber = await ctx.plugin(HttpProxy, {})
  56. try {
  57. expect(HttpProxy.currentProxyPolicy()?.source).toBe('none')
  58. expect(getGlobalDispatcher()).toBe(before)
  59. } finally {
  60. await fiber.dispose()
  61. }
  62. })
  63. it('fails the load when the composition itself declares an unusable proxy', async () => {
  64. const ctx = new Context()
  65. await expect(ctx.plugin(HttpProxy, { httpsProxy: 'socks5://127.0.0.1:1080' }))
  66. .rejects.toThrow(/SOCKS proxy/)
  67. })
  68. })
  69. describe('http-proxy invariant companion', () => {
  70. it('reserves the package name against duplicate registration', async () => {
  71. const ctx = new Context()
  72. await ctx.plugin(InvariantRegistry)
  73. await ctx.plugin(HttpProxyInvariant)
  74. expect(() => {
  75. ctx.invariants.register('@deepseek-ai/dsh-http-proxy', () => {})
  76. }).toThrow(/already registered/)
  77. })
  78. })