返回 oh-my-ppt
proxy.test.ts
根目录 / tests / unit / utils / proxy.test.ts
1 import { beforeEach, describe, expect, it, vi } from 'vitest'
2
3 type MockDispatcher = {
4 kind: string
5 url: string
6 close: ReturnType<typeof vi.fn>
7 }
8
9 const proxyTestState = vi.hoisted(() => {
10 const createDispatcher = (kind: string, url = ''): MockDispatcher => ({
11 kind,
12 url,
13 close: vi.fn().mockResolvedValue(undefined)
14 })
15
16 const originalDispatcher = createDispatcher('original')
17 const createdDispatchers: MockDispatcher[] = []
18 const logMock = {
19 info: vi.fn()
20 }
21 const getGlobalDispatcherMock = vi.fn(() => originalDispatcher)
22 const setGlobalDispatcherMock = vi.fn()
23 const ProxyAgentMock = vi.fn(function (url: string) {
24 const dispatcher = createDispatcher('proxy', url)
25 createdDispatchers.push(dispatcher)
26 return dispatcher
27 })
28 const Socks5ProxyAgentMock = vi.fn(function (url: string) {
29 const dispatcher = createDispatcher('socks', url)
30 createdDispatchers.push(dispatcher)
31 return dispatcher
32 })
33
34 return {
35 createdDispatchers,
36 getGlobalDispatcherMock,
37 logMock,
38 originalDispatcher,
39 ProxyAgentMock,
40 setGlobalDispatcherMock,
41 Socks5ProxyAgentMock
42 }
43 })
44
45 vi.mock('electron-log/main.js', () => ({
46 default: proxyTestState.logMock
47 }))
48
49 vi.mock('undici', () => ({
50 ProxyAgent: proxyTestState.ProxyAgentMock,
51 Socks5ProxyAgent: proxyTestState.Socks5ProxyAgentMock,
52 getGlobalDispatcher: proxyTestState.getGlobalDispatcherMock,
53 setGlobalDispatcher: proxyTestState.setGlobalDispatcherMock
54 }))
55
56 async function loadProxyModule() {
57 vi.resetModules()
58 return import('../../../src/main/utils/proxy')
59 }
60
61 describe('proxy dispatcher management', () => {
62 beforeEach(() => {
63 proxyTestState.createdDispatchers.length = 0
64 proxyTestState.getGlobalDispatcherMock.mockClear()
65 proxyTestState.getGlobalDispatcherMock.mockReturnValue(proxyTestState.originalDispatcher)
66 proxyTestState.logMock.info.mockClear()
67 proxyTestState.originalDispatcher.close.mockClear()
68 proxyTestState.ProxyAgentMock.mockClear()
69 proxyTestState.setGlobalDispatcherMock.mockClear()
70 proxyTestState.Socks5ProxyAgentMock.mockClear()
71 })
72
73 it('applies HTTP proxy via ProxyAgent and trims the URL', async () => {
74 const { applyProxy } = await loadProxyModule()
75
76 applyProxy(' http://127.0.0.1:7890 ')
77
78 expect(proxyTestState.ProxyAgentMock).toHaveBeenCalledWith('http://127.0.0.1:7890')
79 expect(proxyTestState.Socks5ProxyAgentMock).not.toHaveBeenCalled()
80 expect(proxyTestState.setGlobalDispatcherMock).toHaveBeenCalledWith(
81 proxyTestState.createdDispatchers[0]
82 )
83 expect(proxyTestState.logMock.info).toHaveBeenCalledWith(
84 '[proxy] applied',
85 'http://127.0.0.1:7890'
86 )
87 })
88
89 it('applies SOCKS proxy via Socks5ProxyAgent', async () => {
90 const { applyProxy } = await loadProxyModule()
91
92 applyProxy('socks5://127.0.0.1:1080')
93
94 expect(proxyTestState.Socks5ProxyAgentMock).toHaveBeenCalledWith('socks5://127.0.0.1:1080')
95 expect(proxyTestState.ProxyAgentMock).not.toHaveBeenCalled()
96 expect(proxyTestState.createdDispatchers[0]?.kind).toBe('socks')
97 })
98
99 it('closes the previous proxy dispatcher before switching to a new one', async () => {
100 const { applyProxy } = await loadProxyModule()
101
102 applyProxy('http://127.0.0.1:7890')
103 const firstDispatcher = proxyTestState.createdDispatchers[0]
104
105 applyProxy('http://127.0.0.1:7891')
106
107 expect(firstDispatcher.close).toHaveBeenCalledTimes(1)
108 expect(proxyTestState.setGlobalDispatcherMock).toHaveBeenNthCalledWith(
109 2,
110 proxyTestState.createdDispatchers[1]
111 )
112 })
113
114 it('restores the original dispatcher when the proxy URL is empty', async () => {
115 const { applyProxy } = await loadProxyModule()
116
117 applyProxy('http://127.0.0.1:7890')
118 const activeProxyDispatcher = proxyTestState.createdDispatchers[0]
119
120 applyProxy(' ')
121
122 expect(activeProxyDispatcher.close).toHaveBeenCalledTimes(1)
123 expect(proxyTestState.setGlobalDispatcherMock).toHaveBeenLastCalledWith(
124 proxyTestState.originalDispatcher
125 )
126 expect(proxyTestState.logMock.info).toHaveBeenCalledWith(
127 '[proxy] cleared, restored default dispatcher'
128 )
129 })
130
131 it('ignores async close rejections from the previous proxy dispatcher', async () => {
132 const { applyProxy, clearProxy } = await loadProxyModule()
133
134 applyProxy('http://127.0.0.1:7890')
135 const activeProxyDispatcher = proxyTestState.createdDispatchers[0]
136 activeProxyDispatcher.close.mockRejectedValueOnce(new Error('close failed'))
137
138 expect(() => clearProxy()).not.toThrow()
139 await Promise.resolve()
140
141 expect(activeProxyDispatcher.close).toHaveBeenCalledTimes(1)
142 expect(proxyTestState.setGlobalDispatcherMock).toHaveBeenLastCalledWith(
143 proxyTestState.originalDispatcher
144 )
145 })
146 })
147
147 lines TYPESCRIPT