plugin.spec.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import { afterEach, beforeEach, 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 * as HttpProxy from '../src/index.ts'
  6. import * as HttpProxyInvariant from '../src/invariant.ts'
  7. const PROXY = 'http://127.0.0.1:7897'
  8. /**
  9. * Every proxy name in both casings. The suite clears all of them so a developer's own exported proxy
  10. * cannot decide the outcome — the lowercase names matter most, since resolution reads those first.
  11. */
  12. const PROXY_ENV_NAMES = [
  13. 'http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY',
  14. 'no_proxy', 'NO_PROXY', 'all_proxy', 'ALL_PROXY',
  15. ] as const
  16. let saved: Record<string, string | undefined> = {}
  17. beforeEach(() => {
  18. saved = Object.fromEntries(PROXY_ENV_NAMES.map(name => [name, process.env[name]]))
  19. for (const name of PROXY_ENV_NAMES) Reflect.deleteProperty(process.env, name)
  20. })
  21. afterEach(() => {
  22. for (const [name, value] of Object.entries(saved)) {
  23. if (value === undefined) Reflect.deleteProperty(process.env, name)
  24. else process.env[name] = value
  25. }
  26. })
  27. /** The launcher normally provides a snapshot; without one the plugin reads the process environment. */
  28. function withEnv(values: Record<string, string>): void {
  29. for (const [name, value] of Object.entries(values)) process.env[name] = value
  30. }
  31. describe('http-proxy plugin', () => {
  32. it('installs the configured policy and restores the dispatcher on disposal', async () => {
  33. const before = getGlobalDispatcher()
  34. const ctx = new Context()
  35. const fiber = await ctx.plugin(HttpProxy, { httpProxy: PROXY })
  36. expect(HttpProxy.currentProxyPolicy()?.httpProxy).toBe(PROXY)
  37. expect(getGlobalDispatcher()).not.toBe(before)
  38. await fiber.dispose()
  39. expect(HttpProxy.currentProxyPolicy()).toBeUndefined()
  40. expect(getGlobalDispatcher()).toBe(before)
  41. })
  42. it('lets a real environment variable outrank the configured proxy', async () => {
  43. withEnv({ HTTP_PROXY: PROXY })
  44. const ctx = new Context()
  45. const fiber = await ctx.plugin(HttpProxy, { httpProxy: 'http://127.0.0.1:9' })
  46. try {
  47. expect(HttpProxy.currentProxyPolicy()?.httpProxy).toBe(PROXY)
  48. } finally {
  49. await fiber.dispose()
  50. }
  51. })
  52. it('installs nothing under mode off, even with a proxy in the environment', async () => {
  53. withEnv({ HTTP_PROXY: PROXY })
  54. const before = getGlobalDispatcher()
  55. const ctx = new Context()
  56. const fiber = await ctx.plugin(HttpProxy, { mode: 'off' })
  57. try {
  58. expect(HttpProxy.currentProxyPolicy()?.source).toBe('none')
  59. expect(getGlobalDispatcher()).toBe(before)
  60. } finally {
  61. await fiber.dispose()
  62. }
  63. })
  64. it('reports an unusable environment value and connects directly instead of failing the load', async () => {
  65. withEnv({ HTTP_PROXY: 'socks5://127.0.0.1:1080' })
  66. const before = getGlobalDispatcher()
  67. const ctx = new Context()
  68. const fiber = await ctx.plugin(HttpProxy, {})
  69. try {
  70. expect(HttpProxy.currentProxyPolicy()?.source).toBe('none')
  71. expect(getGlobalDispatcher()).toBe(before)
  72. } finally {
  73. await fiber.dispose()
  74. }
  75. })
  76. it('fails the load when the composition itself declares an unusable proxy', async () => {
  77. const ctx = new Context()
  78. await expect(ctx.plugin(HttpProxy, { httpsProxy: 'socks5://127.0.0.1:1080' }))
  79. .rejects.toThrow(/SOCKS proxy/)
  80. })
  81. })
  82. describe('http-proxy invariant companion', () => {
  83. it('reserves the package name against duplicate registration', async () => {
  84. const ctx = new Context()
  85. await ctx.plugin(InvariantRegistry)
  86. await ctx.plugin(HttpProxyInvariant)
  87. expect(() => {
  88. ctx.invariants.register('@deepseek-ai/dsh-http-proxy', () => {})
  89. }).toThrow(/already registered/)
  90. })
  91. })