@yuants/vendor-gate 0.9.4 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. package/dist/api/http-client.js +41 -13
  2. package/dist/api/http-client.js.map +1 -1
  3. package/dist/services/accounts/earning.test.js +17 -8
  4. package/dist/services/accounts/earning.test.js.map +1 -1
  5. package/lib/api/http-client.d.ts.map +1 -1
  6. package/lib/api/http-client.js +40 -12
  7. package/lib/api/http-client.js.map +1 -1
  8. package/lib/services/accounts/earning.test.d.ts +2 -1
  9. package/lib/services/accounts/earning.test.d.ts.map +1 -1
  10. package/lib/services/accounts/earning.test.js +20 -46
  11. package/lib/services/accounts/earning.test.js.map +1 -1
  12. package/package.json +5 -5
  13. package/temp/build/typescript/ts_Eujh_fX2.json +1 -1
  14. package/temp/package-deps.json +6 -6
  15. package/temp/test/jest/haste-map-ac984f375ffe2771b37aa116ca7fe6f5-87fb4bb14df3d29fb61369ce8cbe1fe1-6499ab22de0ac9ce813ded0e96430273 +0 -0
  16. package/temp/test/jest/perf-cache-ac984f375ffe2771b37aa116ca7fe6f5-da39a3ee5e6b4b0d3255bfef95601890 +1 -1
  17. package/temp/test/jest/jest-transform-cache-ac984f375ffe2771b37aa116ca7fe6f5-79ef2876fae7ca75eedb2aa53dc48338/66/package_6697a9305c30c22edf5230ce3e6ca298 +0 -37
  18. package/temp/test/jest/jest-transform-cache-ac984f375ffe2771b37aa116ca7fe6f5-79ef2876fae7ca75eedb2aa53dc48338/b7/jsonschemadraft07_b705a3d042ef36d2b67031ae29c0bb33 +0 -152
  19. package/temp/test/jest/jest-transform-cache-ac984f375ffe2771b37aa116ca7fe6f5-79ef2876fae7ca75eedb2aa53dc48338/d0/package_d0c5a56e46a984794e49571234008aba +0 -36
  20. package/temp/test/jest/jest-transform-cache-ac984f375ffe2771b37aa116ca7fe6f5-79ef2876fae7ca75eedb2aa53dc48338/f0/data_f0eacce12c5ae84cef829948b1c9bf38 +0 -14
@@ -1,13 +1,37 @@
1
1
  var _a;
2
- import { fetch } from '@yuants/http-services';
2
+ import { fetch, selectHTTPProxyIpRoundRobin } from '@yuants/http-services';
3
+ import { Terminal } from '@yuants/protocol';
3
4
  import { encodeHex, formatTime, HmacSHA512, sha512 } from '@yuants/utils';
4
5
  import { join } from 'path';
5
6
  const BASE_URL = 'https://api.gateio.ws/api/v4';
6
7
  const shouldUseHttpProxy = process.env.USE_HTTP_PROXY === 'true';
7
8
  const fetchImpl = shouldUseHttpProxy ? fetch : (_a = globalThis.fetch) !== null && _a !== void 0 ? _a : fetch;
9
+ const terminal = Terminal.fromNodeEnv();
10
+ const MISSING_PUBLIC_IP_LOG_INTERVAL = 3600000;
11
+ const missingPublicIpLogAtByTerminalId = new Map();
8
12
  if (shouldUseHttpProxy) {
9
13
  globalThis.fetch = fetch;
10
14
  }
15
+ const resolveLocalPublicIp = () => {
16
+ var _a, _b, _c;
17
+ const ip = (_b = (_a = terminal.terminalInfo.tags) === null || _a === void 0 ? void 0 : _a.public_ip) === null || _b === void 0 ? void 0 : _b.trim();
18
+ if (ip)
19
+ return ip;
20
+ const now = Date.now();
21
+ const lastLoggedAt = (_c = missingPublicIpLogAtByTerminalId.get(terminal.terminal_id)) !== null && _c !== void 0 ? _c : 0;
22
+ if (now - lastLoggedAt > MISSING_PUBLIC_IP_LOG_INTERVAL) {
23
+ missingPublicIpLogAtByTerminalId.set(terminal.terminal_id, now);
24
+ console.info(formatTime(Date.now()), 'missing terminal public_ip tag, fallback to public-ip-unknown');
25
+ }
26
+ return 'public-ip-unknown';
27
+ };
28
+ const createRequestContext = () => {
29
+ if (shouldUseHttpProxy) {
30
+ const ip = selectHTTPProxyIpRoundRobin(terminal);
31
+ return { ip };
32
+ }
33
+ return { ip: resolveLocalPublicIp() };
34
+ };
11
35
  const serializeQueryParams = (params) => {
12
36
  if (!params)
13
37
  return undefined;
@@ -64,6 +88,7 @@ const parseJSON = async (response, path, params) => {
64
88
  };
65
89
  export const requestPublic = async (method, path, params) => {
66
90
  const { url, body } = createRequestArtifacts(method, path, params);
91
+ const requestContext = createRequestContext();
67
92
  const headers = {
68
93
  Accept: 'application/json',
69
94
  };
@@ -78,12 +103,13 @@ export const requestPublic = async (method, path, params) => {
78
103
  abortController.abort(new Error(`Request timeout after ${timeoutMs}ms`));
79
104
  }, timeoutMs);
80
105
  try {
81
- const response = await fetchImpl(url.href, {
82
- method,
83
- headers,
84
- body: body || undefined,
85
- signal: abortController.signal,
86
- });
106
+ const response = await fetchImpl(url.href, Object.assign({ method,
107
+ headers, body: body || undefined, signal: abortController.signal }, (shouldUseHttpProxy
108
+ ? {
109
+ labels: requestContext.ip ? { ip: requestContext.ip } : undefined,
110
+ terminal,
111
+ }
112
+ : {})));
87
113
  return await parseJSON(response, path, params);
88
114
  }
89
115
  finally {
@@ -92,6 +118,7 @@ export const requestPublic = async (method, path, params) => {
92
118
  };
93
119
  export const requestPrivate = async (credential, method, path, params) => {
94
120
  const { url, body } = createRequestArtifacts(method, path, params);
121
+ const requestContext = createRequestContext();
95
122
  const timestamp = Date.now() / 1000;
96
123
  const bodyDigest = encodeHex(await sha512(new TextEncoder().encode(body)));
97
124
  const signTarget = `${method}\n${url.pathname}\n${url.searchParams}\n${bodyDigest}\n${timestamp}`;
@@ -121,12 +148,13 @@ export const requestPrivate = async (credential, method, path, params) => {
121
148
  abortController.abort(new Error(`Request timeout after ${timeoutMs}ms`));
122
149
  }, timeoutMs);
123
150
  try {
124
- const response = await fetchImpl(url.href, {
125
- method,
126
- headers,
127
- body: body || undefined,
128
- signal: abortController.signal,
129
- });
151
+ const response = await fetchImpl(url.href, Object.assign({ method,
152
+ headers, body: body || undefined, signal: abortController.signal }, (shouldUseHttpProxy
153
+ ? {
154
+ labels: requestContext.ip ? { ip: requestContext.ip } : undefined,
155
+ terminal,
156
+ }
157
+ : {})));
130
158
  return await parseJSON(response, path, params);
131
159
  }
132
160
  finally {
@@ -1 +1 @@
1
- {"version":3,"file":"http-client.js","sourceRoot":"","sources":["../../src/api/http-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { fetch } from '@yuants/http-services';\nimport { encodeHex, formatTime, HmacSHA512, sha512 } from '@yuants/utils';\nimport { join } from 'path';\n\nconst BASE_URL = 'https://api.gateio.ws/api/v4';\nconst shouldUseHttpProxy = process.env.USE_HTTP_PROXY === 'true';\nconst fetchImpl = shouldUseHttpProxy ? fetch : globalThis.fetch ?? fetch;\n\nif (shouldUseHttpProxy) {\n globalThis.fetch = fetch;\n}\n\nexport type HttpMethod = 'GET' | 'POST' | 'DELETE' | 'PUT';\n\nexport type GateParams = Record<string, string | number | boolean | undefined>;\n\nexport interface IGateCredential {\n access_key: string;\n secret_key: string;\n}\n\ninterface IRequestArtifacts {\n url: URL;\n body: string;\n}\n\nconst serializeQueryParams = (params?: GateParams): Record<string, string> | undefined => {\n if (!params) return undefined;\n const normalizedEntries = Object.entries(params).filter(([, value]) => value !== undefined);\n if (normalizedEntries.length === 0) {\n return undefined;\n }\n return Object.fromEntries(normalizedEntries.map(([key, value]) => [key, `${value}`]));\n};\n\nconst createRequestArtifacts = (method: HttpMethod, path: string, params?: GateParams): IRequestArtifacts => {\n const url = new URL(BASE_URL);\n url.pathname = join(url.pathname, path);\n const searchParams = serializeQueryParams(params);\n if (method === 'GET' && searchParams) {\n Object.entries(searchParams).forEach(([key, value]) => url.searchParams.set(key, value));\n }\n const rawBody = method === 'GET' ? '' : JSON.stringify(params);\n const body = rawBody;\n return { url, body };\n};\n\nconst toHeaderObject = (headers: Headers): Record<string, string> => {\n const iterable = headers as unknown as Iterable<[string, string]>;\n return Object.fromEntries(Array.from(iterable));\n};\n\nconst parseJSON = async <TResponse>(\n response: Response,\n path: string,\n params?: GateParams,\n): Promise<TResponse> => {\n const text = await response.text();\n if (process.env.LOG_LEVEL === 'DEBUG') {\n console.debug(formatTime(Date.now()), 'GateResponse', path, JSON.stringify(params ?? {}), text, {\n status: response.status,\n headers: toHeaderObject(response.headers),\n });\n }\n try {\n return JSON.parse(text) as TResponse;\n } catch (error) {\n // 只在 DEBUG 模式下打印完整响应文本,避免泄露敏感信息\n if (process.env.LOG_LEVEL === 'DEBUG') {\n console.debug(formatTime(Date.now()), 'GateRequestFailed', path, JSON.stringify(params ?? {}), text, {\n status: response.status,\n headers: toHeaderObject(response.headers),\n });\n } else {\n // 非 DEBUG 模式下只打印摘要\n const textPreview = text.length > 100 ? text.substring(0, 100) + '...' : text;\n console.info(\n formatTime(Date.now()),\n 'GateRequestFailed',\n path,\n JSON.stringify(params ?? {}),\n textPreview,\n {\n status: response.status,\n headers: toHeaderObject(response.headers),\n },\n );\n }\n throw error;\n }\n};\n\nexport const requestPublic = async <TResponse>(\n method: HttpMethod,\n path: string,\n params?: GateParams,\n): Promise<TResponse> => {\n const { url, body } = createRequestArtifacts(method, path, params);\n const headers: Record<string, string> = {\n Accept: 'application/json',\n };\n if (body) {\n headers['Content-Type'] = 'application/json';\n }\n console.info(formatTime(Date.now()), method, url.href);\n\n // 添加请求超时控制(30秒)\n const timeoutMs = 30_000;\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort(new Error(`Request timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n\n try {\n const response = await fetchImpl(url.href, {\n method,\n headers,\n body: body || undefined,\n signal: abortController.signal,\n });\n return await parseJSON<TResponse>(response, path, params);\n } finally {\n clearTimeout(timeoutId);\n }\n};\n\nexport const requestPrivate = async <TResponse>(\n credential: IGateCredential,\n method: HttpMethod,\n path: string,\n params?: GateParams,\n): Promise<TResponse> => {\n const { url, body } = createRequestArtifacts(method, path, params);\n const timestamp = Date.now() / 1000;\n\n const bodyDigest = encodeHex(await sha512(new TextEncoder().encode(body)));\n const signTarget = `${method}\\n${url.pathname}\\n${url.searchParams}\\n${bodyDigest}\\n${timestamp}`;\n const sign = encodeHex(\n await HmacSHA512(new TextEncoder().encode(signTarget), new TextEncoder().encode(credential.secret_key)),\n );\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n KEY: credential.access_key,\n SIGN: sign,\n Timestamp: `${timestamp}`,\n };\n\n // Add Channel ID header if exists\n if (process.env.CHANNEL_ID) {\n headers['X-Gate-Channel-Id'] = process.env.CHANNEL_ID;\n }\n\n // 安全日志:过滤敏感头信息\n const safeHeaders = { ...headers };\n if (safeHeaders.KEY) safeHeaders.KEY = '***';\n if (safeHeaders.SIGN) safeHeaders.SIGN = '***';\n console.info(formatTime(Date.now()), method, url.href, JSON.stringify(safeHeaders), body);\n\n // 添加请求超时控制(30秒)\n const timeoutMs = 30_000;\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort(new Error(`Request timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n\n try {\n const response = await fetchImpl(url.href, {\n method,\n headers,\n body: body || undefined,\n signal: abortController.signal,\n });\n return await parseJSON<TResponse>(response, path, params);\n } finally {\n clearTimeout(timeoutId);\n }\n};\n"]}
1
+ {"version":3,"file":"http-client.js","sourceRoot":"","sources":["../../src/api/http-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { fetch, selectHTTPProxyIpRoundRobin } from '@yuants/http-services';\nimport { Terminal } from '@yuants/protocol';\nimport { encodeHex, formatTime, HmacSHA512, sha512 } from '@yuants/utils';\nimport { join } from 'path';\n\nconst BASE_URL = 'https://api.gateio.ws/api/v4';\nconst shouldUseHttpProxy = process.env.USE_HTTP_PROXY === 'true';\nconst fetchImpl = shouldUseHttpProxy ? fetch : globalThis.fetch ?? fetch;\nconst terminal = Terminal.fromNodeEnv();\nconst MISSING_PUBLIC_IP_LOG_INTERVAL = 3_600_000;\nconst missingPublicIpLogAtByTerminalId = new Map<string, number>();\n\nif (shouldUseHttpProxy) {\n globalThis.fetch = fetch;\n}\n\nexport type HttpMethod = 'GET' | 'POST' | 'DELETE' | 'PUT';\n\nexport type GateParams = Record<string, string | number | boolean | undefined>;\n\nexport interface IGateCredential {\n access_key: string;\n secret_key: string;\n}\n\ntype RequestContext = { ip: string };\n\nconst resolveLocalPublicIp = (): string => {\n const ip = terminal.terminalInfo.tags?.public_ip?.trim();\n if (ip) return ip;\n const now = Date.now();\n const lastLoggedAt = missingPublicIpLogAtByTerminalId.get(terminal.terminal_id) ?? 0;\n if (now - lastLoggedAt > MISSING_PUBLIC_IP_LOG_INTERVAL) {\n missingPublicIpLogAtByTerminalId.set(terminal.terminal_id, now);\n console.info(formatTime(Date.now()), 'missing terminal public_ip tag, fallback to public-ip-unknown');\n }\n return 'public-ip-unknown';\n};\n\nconst createRequestContext = (): RequestContext => {\n if (shouldUseHttpProxy) {\n const ip = selectHTTPProxyIpRoundRobin(terminal);\n return { ip };\n }\n return { ip: resolveLocalPublicIp() };\n};\n\ninterface IRequestArtifacts {\n url: URL;\n body: string;\n}\n\nconst serializeQueryParams = (params?: GateParams): Record<string, string> | undefined => {\n if (!params) return undefined;\n const normalizedEntries = Object.entries(params).filter(([, value]) => value !== undefined);\n if (normalizedEntries.length === 0) {\n return undefined;\n }\n return Object.fromEntries(normalizedEntries.map(([key, value]) => [key, `${value}`]));\n};\n\nconst createRequestArtifacts = (method: HttpMethod, path: string, params?: GateParams): IRequestArtifacts => {\n const url = new URL(BASE_URL);\n url.pathname = join(url.pathname, path);\n const searchParams = serializeQueryParams(params);\n if (method === 'GET' && searchParams) {\n Object.entries(searchParams).forEach(([key, value]) => url.searchParams.set(key, value));\n }\n const rawBody = method === 'GET' ? '' : JSON.stringify(params);\n const body = rawBody;\n return { url, body };\n};\n\nconst toHeaderObject = (headers: Headers): Record<string, string> => {\n const iterable = headers as unknown as Iterable<[string, string]>;\n return Object.fromEntries(Array.from(iterable));\n};\n\nconst parseJSON = async <TResponse>(\n response: Response,\n path: string,\n params?: GateParams,\n): Promise<TResponse> => {\n const text = await response.text();\n if (process.env.LOG_LEVEL === 'DEBUG') {\n console.debug(formatTime(Date.now()), 'GateResponse', path, JSON.stringify(params ?? {}), text, {\n status: response.status,\n headers: toHeaderObject(response.headers),\n });\n }\n try {\n return JSON.parse(text) as TResponse;\n } catch (error) {\n // 只在 DEBUG 模式下打印完整响应文本,避免泄露敏感信息\n if (process.env.LOG_LEVEL === 'DEBUG') {\n console.debug(formatTime(Date.now()), 'GateRequestFailed', path, JSON.stringify(params ?? {}), text, {\n status: response.status,\n headers: toHeaderObject(response.headers),\n });\n } else {\n // 非 DEBUG 模式下只打印摘要\n const textPreview = text.length > 100 ? text.substring(0, 100) + '...' : text;\n console.info(\n formatTime(Date.now()),\n 'GateRequestFailed',\n path,\n JSON.stringify(params ?? {}),\n textPreview,\n {\n status: response.status,\n headers: toHeaderObject(response.headers),\n },\n );\n }\n throw error;\n }\n};\n\nexport const requestPublic = async <TResponse>(\n method: HttpMethod,\n path: string,\n params?: GateParams,\n): Promise<TResponse> => {\n const { url, body } = createRequestArtifacts(method, path, params);\n const requestContext = createRequestContext();\n const headers: Record<string, string> = {\n Accept: 'application/json',\n };\n if (body) {\n headers['Content-Type'] = 'application/json';\n }\n console.info(formatTime(Date.now()), method, url.href);\n\n // 添加请求超时控制(30秒)\n const timeoutMs = 30_000;\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort(new Error(`Request timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n\n try {\n const response = await fetchImpl(url.href, {\n method,\n headers,\n body: body || undefined,\n signal: abortController.signal,\n ...(shouldUseHttpProxy\n ? {\n labels: requestContext.ip ? { ip: requestContext.ip } : undefined,\n terminal,\n }\n : {}),\n });\n return await parseJSON<TResponse>(response, path, params);\n } finally {\n clearTimeout(timeoutId);\n }\n};\n\nexport const requestPrivate = async <TResponse>(\n credential: IGateCredential,\n method: HttpMethod,\n path: string,\n params?: GateParams,\n): Promise<TResponse> => {\n const { url, body } = createRequestArtifacts(method, path, params);\n const requestContext = createRequestContext();\n const timestamp = Date.now() / 1000;\n\n const bodyDigest = encodeHex(await sha512(new TextEncoder().encode(body)));\n const signTarget = `${method}\\n${url.pathname}\\n${url.searchParams}\\n${bodyDigest}\\n${timestamp}`;\n const sign = encodeHex(\n await HmacSHA512(new TextEncoder().encode(signTarget), new TextEncoder().encode(credential.secret_key)),\n );\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n KEY: credential.access_key,\n SIGN: sign,\n Timestamp: `${timestamp}`,\n };\n\n // Add Channel ID header if exists\n if (process.env.CHANNEL_ID) {\n headers['X-Gate-Channel-Id'] = process.env.CHANNEL_ID;\n }\n\n // 安全日志:过滤敏感头信息\n const safeHeaders = { ...headers };\n if (safeHeaders.KEY) safeHeaders.KEY = '***';\n if (safeHeaders.SIGN) safeHeaders.SIGN = '***';\n console.info(formatTime(Date.now()), method, url.href, JSON.stringify(safeHeaders), body);\n\n // 添加请求超时控制(30秒)\n const timeoutMs = 30_000;\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort(new Error(`Request timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n\n try {\n const response = await fetchImpl(url.href, {\n method,\n headers,\n body: body || undefined,\n signal: abortController.signal,\n ...(shouldUseHttpProxy\n ? {\n labels: requestContext.ip ? { ip: requestContext.ip } : undefined,\n terminal,\n }\n : {}),\n });\n return await parseJSON<TResponse>(response, path, params);\n } finally {\n clearTimeout(timeoutId);\n }\n};\n"]}
@@ -1,13 +1,22 @@
1
- import { getEarningAccountInfo } from './earning';
2
- // Mock 外部 API 模块
3
- const mockGetEarnBalance = jest.fn();
4
- const mockGetSpotPrice = jest.fn();
5
- jest.mock('../../api/private-api', () => (Object.assign(Object.assign({}, jest.requireActual('../../api/private-api')), { getEarnBalance: mockGetEarnBalance })));
6
- jest.mock('../../api/public-api', () => (Object.assign(Object.assign({}, jest.requireActual('../../api/public-api')), { getSpotPrice: mockGetSpotPrice })));
7
- import * as privateApi from '../../api/private-api';
8
- describe('getEarningAccountInfo', () => {
1
+ "use strict";
2
+ const shouldSkip = !process.env.HOST_URL;
3
+ const describeFn = shouldSkip ? describe.skip : describe;
4
+ describeFn('getEarningAccountInfo', () => {
9
5
  const credential = { access_key: 'test', secret_key: 'test' };
10
6
  const account_id = 'GATE/123/EARNING';
7
+ const mockGetEarnBalance = jest.fn();
8
+ const mockGetSpotPrice = jest.fn();
9
+ let privateApi;
10
+ let publicApi;
11
+ let getEarningAccountInfo;
12
+ beforeAll(() => {
13
+ jest.resetModules();
14
+ jest.doMock('../../api/private-api', () => (Object.assign(Object.assign({}, jest.requireActual('../../api/private-api')), { getEarnBalance: mockGetEarnBalance })));
15
+ jest.doMock('../../api/public-api', () => (Object.assign(Object.assign({}, jest.requireActual('../../api/public-api')), { getSpotPrice: mockGetSpotPrice })));
16
+ privateApi = require('../../api/private-api');
17
+ publicApi = require('../../api/public-api');
18
+ ({ getEarningAccountInfo } = require('./earning'));
19
+ });
11
20
  beforeEach(() => {
12
21
  // 保留 mock 设置
13
22
  });
@@ -1 +1 @@
1
- {"version":3,"file":"earning.test.js","sourceRoot":"","sources":["../../../src/services/accounts/earning.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { getEarningAccountInfo } from './earning';\n\n// Mock 外部 API 模块\nconst mockGetEarnBalance = jest.fn();\nconst mockGetSpotPrice = jest.fn();\n\njest.mock('../../api/private-api', () => ({\n ...jest.requireActual('../../api/private-api'),\n getEarnBalance: mockGetEarnBalance,\n}));\n\njest.mock('../../api/public-api', () => ({\n ...jest.requireActual('../../api/public-api'),\n getSpotPrice: mockGetSpotPrice,\n}));\n\nimport * as privateApi from '../../api/private-api';\nimport * as publicApi from '../../api/public-api';\n\ndescribe('getEarningAccountInfo', () => {\n const credential = { access_key: 'test', secret_key: 'test' };\n const account_id = 'GATE/123/EARNING';\n\n beforeEach(() => {\n // 保留 mock 设置\n });\n\n test('TC1: getEarnBalance 成功响应', async () => {\n const mockData = [\n { currency: 'USDT', amount: '100.5', frozen_amount: '10', lent_amount: '90', current_amount: '100.5' },\n { currency: 'BTC', amount: '0.002', frozen_amount: '0', lent_amount: '0.002', current_amount: '0.002' },\n ];\n mockGetEarnBalance.mockResolvedValue(mockData);\n\n const result = await privateApi.getEarnBalance(credential, {});\n expect(result).toEqual(mockData);\n expect(privateApi.getEarnBalance).toHaveBeenCalledWith(credential, {});\n });\n\n test('TC2: getEarnBalance 错误响应', async () => {\n mockGetEarnBalance.mockRejectedValue(new Error('API Error 401'));\n\n await expect(privateApi.getEarnBalance(credential, {})).rejects.toThrow('API Error 401');\n expect(privateApi.getEarnBalance).toHaveBeenCalledWith(credential, {});\n });\n\n test.skip('TC3: 余额映射 - 正常情况', async () => {\n mockGetEarnBalance.mockResolvedValue([\n { currency: 'USDT', amount: '100.5', frozen_amount: '10', lent_amount: '90', current_amount: '100.5' },\n { currency: 'BTC', amount: '0.002', frozen_amount: '0', lent_amount: '0.002', current_amount: '0.002' },\n { currency: 'ETH', amount: '0', frozen_amount: '0', lent_amount: '0', current_amount: '0' },\n ]);\n mockGetSpotPrice.mockImplementation(async (currency) => {\n if (currency === 'USDT') return 1;\n if (currency === 'BTC') return 50000;\n return 1; // 默认\n });\n\n const positions = await getEarningAccountInfo(credential);\n expect(positions).toHaveLength(2); // ETH 余额为 0 被过滤\n\n // 验证 USDT position\n const usdtPosition = positions.find((p) => p.position_id === 'earning/USDT');\n expect(usdtPosition).toBeDefined();\n expect(usdtPosition!.datasource_id).toBe('GATE');\n expect(usdtPosition!.product_id).toBe('GATE/EARNING/USDT');\n expect(usdtPosition!.volume).toBe(100.5);\n expect(usdtPosition!.free_volume).toBe(90.5); // amount - frozen\n expect(usdtPosition!.closable_price).toBe(1); // USDT 价格为 1\n\n // 验证 BTC position\n const btcPosition = positions.find((p) => p.position_id === 'earning/BTC');\n expect(btcPosition).toBeDefined();\n expect(btcPosition!.datasource_id).toBe('GATE');\n expect(btcPosition!.product_id).toBe('GATE/EARNING/BTC');\n expect(btcPosition!.volume).toBe(0.002);\n expect(btcPosition!.free_volume).toBe(0.002);\n expect(btcPosition!.closable_price).toBe(50000);\n\n // 验证 ETH 被过滤\n const ethPosition = positions.find((p) => p.position_id === 'earning/ETH');\n expect(ethPosition).toBeUndefined();\n });\n\n test.skip('TC4: 价格获取失败 - 回退到价格 1', async () => {\n mockGetEarnBalance.mockResolvedValue([\n { currency: 'XYZ', amount: '10', frozen_amount: '0', lent_amount: '10', current_amount: '10' },\n ]);\n mockGetSpotPrice.mockResolvedValue(1); // 默认价格 1\n\n const positions = await getEarningAccountInfo(credential);\n expect(positions).toHaveLength(1);\n\n const position = positions[0];\n expect(position.position_id).toBe('earning/XYZ');\n expect(position.closable_price).toBe(1); // 默认价格 1\n expect(position.volume).toBe(10);\n expect(position.free_volume).toBe(10);\n });\n\n test.skip('TC5: 价格获取失败 - 特殊币种映射 (SOL2/GTSOL)', async () => {\n mockGetEarnBalance.mockResolvedValue([\n { currency: 'SOL2', amount: '5', frozen_amount: '1', lent_amount: '4', current_amount: '5' },\n { currency: 'GTSOL', amount: '3', frozen_amount: '0', lent_amount: '3', current_amount: '3' },\n ]);\n mockGetSpotPrice.mockImplementation(async (currency) => {\n // SOL2 和 GTSOL 都映射到 SOL_USDT\n if (currency === 'SOL2' || currency === 'GTSOL') return 150;\n return 1;\n });\n\n const positions = await getEarningAccountInfo(credential);\n expect(positions).toHaveLength(2);\n\n // SOL2 应映射到 SOL_USDT\n const sol2Position = positions.find((p) => p.position_id === 'earning/SOL2');\n expect(sol2Position).toBeDefined();\n expect(sol2Position!.closable_price).toBe(150);\n expect(sol2Position!.free_volume).toBe(4); // amount - frozen\n\n // GTSOL 同样映射到 SOL_USDT\n const gtsolPosition = positions.find((p) => p.position_id === 'earning/GTSOL');\n expect(gtsolPosition).toBeDefined();\n expect(gtsolPosition!.closable_price).toBe(150);\n });\n\n test.skip('零余额过滤', async () => {\n mockGetEarnBalance.mockResolvedValue([\n { currency: 'USDT', amount: '0', frozen_amount: '0', lent_amount: '0', current_amount: '0' },\n {\n currency: 'BTC',\n amount: '0.000001',\n frozen_amount: '0',\n lent_amount: '0.000001',\n current_amount: '0.000001',\n },\n ]);\n mockGetSpotPrice.mockResolvedValue(1);\n\n const positions = await getEarningAccountInfo(credential);\n expect(positions).toHaveLength(1); // 只有 BTC 余额 > 0\n expect(positions[0].position_id).toBe('earning/BTC');\n });\n});\n"]}
1
+ {"version":3,"file":"earning.test.js","sourceRoot":"","sources":["../../../src/services/accounts/earning.test.ts"],"names":[],"mappings":"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","sourcesContent":["const shouldSkip = !process.env.HOST_URL;\nconst describeFn = shouldSkip ? describe.skip : describe;\n\ndescribeFn('getEarningAccountInfo', () => {\n const credential = { access_key: 'test', secret_key: 'test' };\n const account_id = 'GATE/123/EARNING';\n const mockGetEarnBalance = jest.fn();\n const mockGetSpotPrice = jest.fn();\n let privateApi: typeof import('../../api/private-api');\n let publicApi: typeof import('../../api/public-api');\n let getEarningAccountInfo: typeof import('./earning').getEarningAccountInfo;\n\n beforeAll(() => {\n jest.resetModules();\n jest.doMock('../../api/private-api', () => ({\n ...jest.requireActual('../../api/private-api'),\n getEarnBalance: mockGetEarnBalance,\n }));\n jest.doMock('../../api/public-api', () => ({\n ...jest.requireActual('../../api/public-api'),\n getSpotPrice: mockGetSpotPrice,\n }));\n privateApi = require('../../api/private-api');\n publicApi = require('../../api/public-api');\n ({ getEarningAccountInfo } = require('./earning'));\n });\n\n beforeEach(() => {\n // 保留 mock 设置\n });\n\n test('TC1: getEarnBalance 成功响应', async () => {\n const mockData = [\n { currency: 'USDT', amount: '100.5', frozen_amount: '10', lent_amount: '90', current_amount: '100.5' },\n { currency: 'BTC', amount: '0.002', frozen_amount: '0', lent_amount: '0.002', current_amount: '0.002' },\n ];\n mockGetEarnBalance.mockResolvedValue(mockData);\n\n const result = await privateApi.getEarnBalance(credential, {});\n expect(result).toEqual(mockData);\n expect(privateApi.getEarnBalance).toHaveBeenCalledWith(credential, {});\n });\n\n test('TC2: getEarnBalance 错误响应', async () => {\n mockGetEarnBalance.mockRejectedValue(new Error('API Error 401'));\n\n await expect(privateApi.getEarnBalance(credential, {})).rejects.toThrow('API Error 401');\n expect(privateApi.getEarnBalance).toHaveBeenCalledWith(credential, {});\n });\n\n test.skip('TC3: 余额映射 - 正常情况', async () => {\n mockGetEarnBalance.mockResolvedValue([\n { currency: 'USDT', amount: '100.5', frozen_amount: '10', lent_amount: '90', current_amount: '100.5' },\n { currency: 'BTC', amount: '0.002', frozen_amount: '0', lent_amount: '0.002', current_amount: '0.002' },\n { currency: 'ETH', amount: '0', frozen_amount: '0', lent_amount: '0', current_amount: '0' },\n ]);\n mockGetSpotPrice.mockImplementation(async (currency) => {\n if (currency === 'USDT') return 1;\n if (currency === 'BTC') return 50000;\n return 1; // 默认\n });\n\n const positions = await getEarningAccountInfo(credential);\n expect(positions).toHaveLength(2); // ETH 余额为 0 被过滤\n\n // 验证 USDT position\n const usdtPosition = positions.find((p) => p.position_id === 'earning/USDT');\n expect(usdtPosition).toBeDefined();\n expect(usdtPosition!.datasource_id).toBe('GATE');\n expect(usdtPosition!.product_id).toBe('GATE/EARNING/USDT');\n expect(usdtPosition!.volume).toBe(100.5);\n expect(usdtPosition!.free_volume).toBe(90.5); // amount - frozen\n expect(usdtPosition!.closable_price).toBe(1); // USDT 价格为 1\n\n // 验证 BTC position\n const btcPosition = positions.find((p) => p.position_id === 'earning/BTC');\n expect(btcPosition).toBeDefined();\n expect(btcPosition!.datasource_id).toBe('GATE');\n expect(btcPosition!.product_id).toBe('GATE/EARNING/BTC');\n expect(btcPosition!.volume).toBe(0.002);\n expect(btcPosition!.free_volume).toBe(0.002);\n expect(btcPosition!.closable_price).toBe(50000);\n\n // 验证 ETH 被过滤\n const ethPosition = positions.find((p) => p.position_id === 'earning/ETH');\n expect(ethPosition).toBeUndefined();\n });\n\n test.skip('TC4: 价格获取失败 - 回退到价格 1', async () => {\n mockGetEarnBalance.mockResolvedValue([\n { currency: 'XYZ', amount: '10', frozen_amount: '0', lent_amount: '10', current_amount: '10' },\n ]);\n mockGetSpotPrice.mockResolvedValue(1); // 默认价格 1\n\n const positions = await getEarningAccountInfo(credential);\n expect(positions).toHaveLength(1);\n\n const position = positions[0];\n expect(position.position_id).toBe('earning/XYZ');\n expect(position.closable_price).toBe(1); // 默认价格 1\n expect(position.volume).toBe(10);\n expect(position.free_volume).toBe(10);\n });\n\n test.skip('TC5: 价格获取失败 - 特殊币种映射 (SOL2/GTSOL)', async () => {\n mockGetEarnBalance.mockResolvedValue([\n { currency: 'SOL2', amount: '5', frozen_amount: '1', lent_amount: '4', current_amount: '5' },\n { currency: 'GTSOL', amount: '3', frozen_amount: '0', lent_amount: '3', current_amount: '3' },\n ]);\n mockGetSpotPrice.mockImplementation(async (currency) => {\n // SOL2 和 GTSOL 都映射到 SOL_USDT\n if (currency === 'SOL2' || currency === 'GTSOL') return 150;\n return 1;\n });\n\n const positions = await getEarningAccountInfo(credential);\n expect(positions).toHaveLength(2);\n\n // SOL2 应映射到 SOL_USDT\n const sol2Position = positions.find((p) => p.position_id === 'earning/SOL2');\n expect(sol2Position).toBeDefined();\n expect(sol2Position!.closable_price).toBe(150);\n expect(sol2Position!.free_volume).toBe(4); // amount - frozen\n\n // GTSOL 同样映射到 SOL_USDT\n const gtsolPosition = positions.find((p) => p.position_id === 'earning/GTSOL');\n expect(gtsolPosition).toBeDefined();\n expect(gtsolPosition!.closable_price).toBe(150);\n });\n\n test.skip('零余额过滤', async () => {\n mockGetEarnBalance.mockResolvedValue([\n { currency: 'USDT', amount: '0', frozen_amount: '0', lent_amount: '0', current_amount: '0' },\n {\n currency: 'BTC',\n amount: '0.000001',\n frozen_amount: '0',\n lent_amount: '0.000001',\n current_amount: '0.000001',\n },\n ]);\n mockGetSpotPrice.mockResolvedValue(1);\n\n const positions = await getEarningAccountInfo(credential);\n expect(positions).toHaveLength(1); // 只有 BTC 余额 > 0\n expect(positions[0].position_id).toBe('earning/BTC');\n });\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../../src/api/http-client.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE3D,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;AAE/E,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAyED,eAAO,MAAM,aAAa,GAAU,SAAS,EAC3C,QAAQ,UAAU,EAClB,MAAM,MAAM,EACZ,SAAS,UAAU,KAClB,OAAO,CAAC,SAAS,CA4BnB,CAAC;AAEF,eAAO,MAAM,cAAc,GAAU,SAAS,EAC5C,YAAY,eAAe,EAC3B,QAAQ,UAAU,EAClB,MAAM,MAAM,EACZ,SAAS,UAAU,KAClB,OAAO,CAAC,SAAS,CA+CnB,CAAC"}
1
+ {"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../../src/api/http-client.ts"],"names":[],"mappings":"AAgBA,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAE3D,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;AAE/E,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AA+FD,eAAO,MAAM,aAAa,GAAU,SAAS,EAC3C,QAAQ,UAAU,EAClB,MAAM,MAAM,EACZ,SAAS,UAAU,KAClB,OAAO,CAAC,SAAS,CAmCnB,CAAC;AAEF,eAAO,MAAM,cAAc,GAAU,SAAS,EAC5C,YAAY,eAAe,EAC3B,QAAQ,UAAU,EAClB,MAAM,MAAM,EACZ,SAAS,UAAU,KAClB,OAAO,CAAC,SAAS,CAsDnB,CAAC"}
@@ -3,14 +3,38 @@ var _a;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.requestPrivate = exports.requestPublic = void 0;
5
5
  const http_services_1 = require("@yuants/http-services");
6
+ const protocol_1 = require("@yuants/protocol");
6
7
  const utils_1 = require("@yuants/utils");
7
8
  const path_1 = require("path");
8
9
  const BASE_URL = 'https://api.gateio.ws/api/v4';
9
10
  const shouldUseHttpProxy = process.env.USE_HTTP_PROXY === 'true';
10
11
  const fetchImpl = shouldUseHttpProxy ? http_services_1.fetch : (_a = globalThis.fetch) !== null && _a !== void 0 ? _a : http_services_1.fetch;
12
+ const terminal = protocol_1.Terminal.fromNodeEnv();
13
+ const MISSING_PUBLIC_IP_LOG_INTERVAL = 3600000;
14
+ const missingPublicIpLogAtByTerminalId = new Map();
11
15
  if (shouldUseHttpProxy) {
12
16
  globalThis.fetch = http_services_1.fetch;
13
17
  }
18
+ const resolveLocalPublicIp = () => {
19
+ var _a, _b, _c;
20
+ const ip = (_b = (_a = terminal.terminalInfo.tags) === null || _a === void 0 ? void 0 : _a.public_ip) === null || _b === void 0 ? void 0 : _b.trim();
21
+ if (ip)
22
+ return ip;
23
+ const now = Date.now();
24
+ const lastLoggedAt = (_c = missingPublicIpLogAtByTerminalId.get(terminal.terminal_id)) !== null && _c !== void 0 ? _c : 0;
25
+ if (now - lastLoggedAt > MISSING_PUBLIC_IP_LOG_INTERVAL) {
26
+ missingPublicIpLogAtByTerminalId.set(terminal.terminal_id, now);
27
+ console.info((0, utils_1.formatTime)(Date.now()), 'missing terminal public_ip tag, fallback to public-ip-unknown');
28
+ }
29
+ return 'public-ip-unknown';
30
+ };
31
+ const createRequestContext = () => {
32
+ if (shouldUseHttpProxy) {
33
+ const ip = (0, http_services_1.selectHTTPProxyIpRoundRobin)(terminal);
34
+ return { ip };
35
+ }
36
+ return { ip: resolveLocalPublicIp() };
37
+ };
14
38
  const serializeQueryParams = (params) => {
15
39
  if (!params)
16
40
  return undefined;
@@ -67,6 +91,7 @@ const parseJSON = async (response, path, params) => {
67
91
  };
68
92
  const requestPublic = async (method, path, params) => {
69
93
  const { url, body } = createRequestArtifacts(method, path, params);
94
+ const requestContext = createRequestContext();
70
95
  const headers = {
71
96
  Accept: 'application/json',
72
97
  };
@@ -81,12 +106,13 @@ const requestPublic = async (method, path, params) => {
81
106
  abortController.abort(new Error(`Request timeout after ${timeoutMs}ms`));
82
107
  }, timeoutMs);
83
108
  try {
84
- const response = await fetchImpl(url.href, {
85
- method,
86
- headers,
87
- body: body || undefined,
88
- signal: abortController.signal,
89
- });
109
+ const response = await fetchImpl(url.href, Object.assign({ method,
110
+ headers, body: body || undefined, signal: abortController.signal }, (shouldUseHttpProxy
111
+ ? {
112
+ labels: requestContext.ip ? { ip: requestContext.ip } : undefined,
113
+ terminal,
114
+ }
115
+ : {})));
90
116
  return await parseJSON(response, path, params);
91
117
  }
92
118
  finally {
@@ -96,6 +122,7 @@ const requestPublic = async (method, path, params) => {
96
122
  exports.requestPublic = requestPublic;
97
123
  const requestPrivate = async (credential, method, path, params) => {
98
124
  const { url, body } = createRequestArtifacts(method, path, params);
125
+ const requestContext = createRequestContext();
99
126
  const timestamp = Date.now() / 1000;
100
127
  const bodyDigest = (0, utils_1.encodeHex)(await (0, utils_1.sha512)(new TextEncoder().encode(body)));
101
128
  const signTarget = `${method}\n${url.pathname}\n${url.searchParams}\n${bodyDigest}\n${timestamp}`;
@@ -125,12 +152,13 @@ const requestPrivate = async (credential, method, path, params) => {
125
152
  abortController.abort(new Error(`Request timeout after ${timeoutMs}ms`));
126
153
  }, timeoutMs);
127
154
  try {
128
- const response = await fetchImpl(url.href, {
129
- method,
130
- headers,
131
- body: body || undefined,
132
- signal: abortController.signal,
133
- });
155
+ const response = await fetchImpl(url.href, Object.assign({ method,
156
+ headers, body: body || undefined, signal: abortController.signal }, (shouldUseHttpProxy
157
+ ? {
158
+ labels: requestContext.ip ? { ip: requestContext.ip } : undefined,
159
+ terminal,
160
+ }
161
+ : {})));
134
162
  return await parseJSON(response, path, params);
135
163
  }
136
164
  finally {
@@ -1 +1 @@
1
- {"version":3,"file":"http-client.js","sourceRoot":"","sources":["../../src/api/http-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { fetch } from '@yuants/http-services';\nimport { encodeHex, formatTime, HmacSHA512, sha512 } from '@yuants/utils';\nimport { join } from 'path';\n\nconst BASE_URL = 'https://api.gateio.ws/api/v4';\nconst shouldUseHttpProxy = process.env.USE_HTTP_PROXY === 'true';\nconst fetchImpl = shouldUseHttpProxy ? fetch : globalThis.fetch ?? fetch;\n\nif (shouldUseHttpProxy) {\n globalThis.fetch = fetch;\n}\n\nexport type HttpMethod = 'GET' | 'POST' | 'DELETE' | 'PUT';\n\nexport type GateParams = Record<string, string | number | boolean | undefined>;\n\nexport interface IGateCredential {\n access_key: string;\n secret_key: string;\n}\n\ninterface IRequestArtifacts {\n url: URL;\n body: string;\n}\n\nconst serializeQueryParams = (params?: GateParams): Record<string, string> | undefined => {\n if (!params) return undefined;\n const normalizedEntries = Object.entries(params).filter(([, value]) => value !== undefined);\n if (normalizedEntries.length === 0) {\n return undefined;\n }\n return Object.fromEntries(normalizedEntries.map(([key, value]) => [key, `${value}`]));\n};\n\nconst createRequestArtifacts = (method: HttpMethod, path: string, params?: GateParams): IRequestArtifacts => {\n const url = new URL(BASE_URL);\n url.pathname = join(url.pathname, path);\n const searchParams = serializeQueryParams(params);\n if (method === 'GET' && searchParams) {\n Object.entries(searchParams).forEach(([key, value]) => url.searchParams.set(key, value));\n }\n const rawBody = method === 'GET' ? '' : JSON.stringify(params);\n const body = rawBody;\n return { url, body };\n};\n\nconst toHeaderObject = (headers: Headers): Record<string, string> => {\n const iterable = headers as unknown as Iterable<[string, string]>;\n return Object.fromEntries(Array.from(iterable));\n};\n\nconst parseJSON = async <TResponse>(\n response: Response,\n path: string,\n params?: GateParams,\n): Promise<TResponse> => {\n const text = await response.text();\n if (process.env.LOG_LEVEL === 'DEBUG') {\n console.debug(formatTime(Date.now()), 'GateResponse', path, JSON.stringify(params ?? {}), text, {\n status: response.status,\n headers: toHeaderObject(response.headers),\n });\n }\n try {\n return JSON.parse(text) as TResponse;\n } catch (error) {\n // 只在 DEBUG 模式下打印完整响应文本,避免泄露敏感信息\n if (process.env.LOG_LEVEL === 'DEBUG') {\n console.debug(formatTime(Date.now()), 'GateRequestFailed', path, JSON.stringify(params ?? {}), text, {\n status: response.status,\n headers: toHeaderObject(response.headers),\n });\n } else {\n // 非 DEBUG 模式下只打印摘要\n const textPreview = text.length > 100 ? text.substring(0, 100) + '...' : text;\n console.info(\n formatTime(Date.now()),\n 'GateRequestFailed',\n path,\n JSON.stringify(params ?? {}),\n textPreview,\n {\n status: response.status,\n headers: toHeaderObject(response.headers),\n },\n );\n }\n throw error;\n }\n};\n\nexport const requestPublic = async <TResponse>(\n method: HttpMethod,\n path: string,\n params?: GateParams,\n): Promise<TResponse> => {\n const { url, body } = createRequestArtifacts(method, path, params);\n const headers: Record<string, string> = {\n Accept: 'application/json',\n };\n if (body) {\n headers['Content-Type'] = 'application/json';\n }\n console.info(formatTime(Date.now()), method, url.href);\n\n // 添加请求超时控制(30秒)\n const timeoutMs = 30_000;\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort(new Error(`Request timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n\n try {\n const response = await fetchImpl(url.href, {\n method,\n headers,\n body: body || undefined,\n signal: abortController.signal,\n });\n return await parseJSON<TResponse>(response, path, params);\n } finally {\n clearTimeout(timeoutId);\n }\n};\n\nexport const requestPrivate = async <TResponse>(\n credential: IGateCredential,\n method: HttpMethod,\n path: string,\n params?: GateParams,\n): Promise<TResponse> => {\n const { url, body } = createRequestArtifacts(method, path, params);\n const timestamp = Date.now() / 1000;\n\n const bodyDigest = encodeHex(await sha512(new TextEncoder().encode(body)));\n const signTarget = `${method}\\n${url.pathname}\\n${url.searchParams}\\n${bodyDigest}\\n${timestamp}`;\n const sign = encodeHex(\n await HmacSHA512(new TextEncoder().encode(signTarget), new TextEncoder().encode(credential.secret_key)),\n );\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n KEY: credential.access_key,\n SIGN: sign,\n Timestamp: `${timestamp}`,\n };\n\n // Add Channel ID header if exists\n if (process.env.CHANNEL_ID) {\n headers['X-Gate-Channel-Id'] = process.env.CHANNEL_ID;\n }\n\n // 安全日志:过滤敏感头信息\n const safeHeaders = { ...headers };\n if (safeHeaders.KEY) safeHeaders.KEY = '***';\n if (safeHeaders.SIGN) safeHeaders.SIGN = '***';\n console.info(formatTime(Date.now()), method, url.href, JSON.stringify(safeHeaders), body);\n\n // 添加请求超时控制(30秒)\n const timeoutMs = 30_000;\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort(new Error(`Request timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n\n try {\n const response = await fetchImpl(url.href, {\n method,\n headers,\n body: body || undefined,\n signal: abortController.signal,\n });\n return await parseJSON<TResponse>(response, path, params);\n } finally {\n clearTimeout(timeoutId);\n }\n};\n"]}
1
+ {"version":3,"file":"http-client.js","sourceRoot":"","sources":["../../src/api/http-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { fetch, selectHTTPProxyIpRoundRobin } from '@yuants/http-services';\nimport { Terminal } from '@yuants/protocol';\nimport { encodeHex, formatTime, HmacSHA512, sha512 } from '@yuants/utils';\nimport { join } from 'path';\n\nconst BASE_URL = 'https://api.gateio.ws/api/v4';\nconst shouldUseHttpProxy = process.env.USE_HTTP_PROXY === 'true';\nconst fetchImpl = shouldUseHttpProxy ? fetch : globalThis.fetch ?? fetch;\nconst terminal = Terminal.fromNodeEnv();\nconst MISSING_PUBLIC_IP_LOG_INTERVAL = 3_600_000;\nconst missingPublicIpLogAtByTerminalId = new Map<string, number>();\n\nif (shouldUseHttpProxy) {\n globalThis.fetch = fetch;\n}\n\nexport type HttpMethod = 'GET' | 'POST' | 'DELETE' | 'PUT';\n\nexport type GateParams = Record<string, string | number | boolean | undefined>;\n\nexport interface IGateCredential {\n access_key: string;\n secret_key: string;\n}\n\ntype RequestContext = { ip: string };\n\nconst resolveLocalPublicIp = (): string => {\n const ip = terminal.terminalInfo.tags?.public_ip?.trim();\n if (ip) return ip;\n const now = Date.now();\n const lastLoggedAt = missingPublicIpLogAtByTerminalId.get(terminal.terminal_id) ?? 0;\n if (now - lastLoggedAt > MISSING_PUBLIC_IP_LOG_INTERVAL) {\n missingPublicIpLogAtByTerminalId.set(terminal.terminal_id, now);\n console.info(formatTime(Date.now()), 'missing terminal public_ip tag, fallback to public-ip-unknown');\n }\n return 'public-ip-unknown';\n};\n\nconst createRequestContext = (): RequestContext => {\n if (shouldUseHttpProxy) {\n const ip = selectHTTPProxyIpRoundRobin(terminal);\n return { ip };\n }\n return { ip: resolveLocalPublicIp() };\n};\n\ninterface IRequestArtifacts {\n url: URL;\n body: string;\n}\n\nconst serializeQueryParams = (params?: GateParams): Record<string, string> | undefined => {\n if (!params) return undefined;\n const normalizedEntries = Object.entries(params).filter(([, value]) => value !== undefined);\n if (normalizedEntries.length === 0) {\n return undefined;\n }\n return Object.fromEntries(normalizedEntries.map(([key, value]) => [key, `${value}`]));\n};\n\nconst createRequestArtifacts = (method: HttpMethod, path: string, params?: GateParams): IRequestArtifacts => {\n const url = new URL(BASE_URL);\n url.pathname = join(url.pathname, path);\n const searchParams = serializeQueryParams(params);\n if (method === 'GET' && searchParams) {\n Object.entries(searchParams).forEach(([key, value]) => url.searchParams.set(key, value));\n }\n const rawBody = method === 'GET' ? '' : JSON.stringify(params);\n const body = rawBody;\n return { url, body };\n};\n\nconst toHeaderObject = (headers: Headers): Record<string, string> => {\n const iterable = headers as unknown as Iterable<[string, string]>;\n return Object.fromEntries(Array.from(iterable));\n};\n\nconst parseJSON = async <TResponse>(\n response: Response,\n path: string,\n params?: GateParams,\n): Promise<TResponse> => {\n const text = await response.text();\n if (process.env.LOG_LEVEL === 'DEBUG') {\n console.debug(formatTime(Date.now()), 'GateResponse', path, JSON.stringify(params ?? {}), text, {\n status: response.status,\n headers: toHeaderObject(response.headers),\n });\n }\n try {\n return JSON.parse(text) as TResponse;\n } catch (error) {\n // 只在 DEBUG 模式下打印完整响应文本,避免泄露敏感信息\n if (process.env.LOG_LEVEL === 'DEBUG') {\n console.debug(formatTime(Date.now()), 'GateRequestFailed', path, JSON.stringify(params ?? {}), text, {\n status: response.status,\n headers: toHeaderObject(response.headers),\n });\n } else {\n // 非 DEBUG 模式下只打印摘要\n const textPreview = text.length > 100 ? text.substring(0, 100) + '...' : text;\n console.info(\n formatTime(Date.now()),\n 'GateRequestFailed',\n path,\n JSON.stringify(params ?? {}),\n textPreview,\n {\n status: response.status,\n headers: toHeaderObject(response.headers),\n },\n );\n }\n throw error;\n }\n};\n\nexport const requestPublic = async <TResponse>(\n method: HttpMethod,\n path: string,\n params?: GateParams,\n): Promise<TResponse> => {\n const { url, body } = createRequestArtifacts(method, path, params);\n const requestContext = createRequestContext();\n const headers: Record<string, string> = {\n Accept: 'application/json',\n };\n if (body) {\n headers['Content-Type'] = 'application/json';\n }\n console.info(formatTime(Date.now()), method, url.href);\n\n // 添加请求超时控制(30秒)\n const timeoutMs = 30_000;\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort(new Error(`Request timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n\n try {\n const response = await fetchImpl(url.href, {\n method,\n headers,\n body: body || undefined,\n signal: abortController.signal,\n ...(shouldUseHttpProxy\n ? {\n labels: requestContext.ip ? { ip: requestContext.ip } : undefined,\n terminal,\n }\n : {}),\n });\n return await parseJSON<TResponse>(response, path, params);\n } finally {\n clearTimeout(timeoutId);\n }\n};\n\nexport const requestPrivate = async <TResponse>(\n credential: IGateCredential,\n method: HttpMethod,\n path: string,\n params?: GateParams,\n): Promise<TResponse> => {\n const { url, body } = createRequestArtifacts(method, path, params);\n const requestContext = createRequestContext();\n const timestamp = Date.now() / 1000;\n\n const bodyDigest = encodeHex(await sha512(new TextEncoder().encode(body)));\n const signTarget = `${method}\\n${url.pathname}\\n${url.searchParams}\\n${bodyDigest}\\n${timestamp}`;\n const sign = encodeHex(\n await HmacSHA512(new TextEncoder().encode(signTarget), new TextEncoder().encode(credential.secret_key)),\n );\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n KEY: credential.access_key,\n SIGN: sign,\n Timestamp: `${timestamp}`,\n };\n\n // Add Channel ID header if exists\n if (process.env.CHANNEL_ID) {\n headers['X-Gate-Channel-Id'] = process.env.CHANNEL_ID;\n }\n\n // 安全日志:过滤敏感头信息\n const safeHeaders = { ...headers };\n if (safeHeaders.KEY) safeHeaders.KEY = '***';\n if (safeHeaders.SIGN) safeHeaders.SIGN = '***';\n console.info(formatTime(Date.now()), method, url.href, JSON.stringify(safeHeaders), body);\n\n // 添加请求超时控制(30秒)\n const timeoutMs = 30_000;\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort(new Error(`Request timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n\n try {\n const response = await fetchImpl(url.href, {\n method,\n headers,\n body: body || undefined,\n signal: abortController.signal,\n ...(shouldUseHttpProxy\n ? {\n labels: requestContext.ip ? { ip: requestContext.ip } : undefined,\n terminal,\n }\n : {}),\n });\n return await parseJSON<TResponse>(response, path, params);\n } finally {\n clearTimeout(timeoutId);\n }\n};\n"]}
@@ -1,2 +1,3 @@
1
- export {};
1
+ declare const shouldSkip: boolean;
2
+ declare const describeFn: jest.Describe;
2
3
  //# sourceMappingURL=earning.test.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"earning.test.d.ts","sourceRoot":"","sources":["../../../src/services/accounts/earning.test.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"earning.test.d.ts","sourceRoot":"","sources":["../../../src/services/accounts/earning.test.ts"],"names":[],"mappings":"AAAA,QAAA,MAAM,UAAU,SAAwB,CAAC;AACzC,QAAA,MAAM,UAAU,eAAwC,CAAC"}
@@ -1,48 +1,22 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- const earning_1 = require("./earning");
37
- // Mock 外部 API 模块
38
- const mockGetEarnBalance = jest.fn();
39
- const mockGetSpotPrice = jest.fn();
40
- jest.mock('../../api/private-api', () => (Object.assign(Object.assign({}, jest.requireActual('../../api/private-api')), { getEarnBalance: mockGetEarnBalance })));
41
- jest.mock('../../api/public-api', () => (Object.assign(Object.assign({}, jest.requireActual('../../api/public-api')), { getSpotPrice: mockGetSpotPrice })));
42
- const privateApi = __importStar(require("../../api/private-api"));
43
- describe('getEarningAccountInfo', () => {
2
+ const shouldSkip = !process.env.HOST_URL;
3
+ const describeFn = shouldSkip ? describe.skip : describe;
4
+ describeFn('getEarningAccountInfo', () => {
44
5
  const credential = { access_key: 'test', secret_key: 'test' };
45
6
  const account_id = 'GATE/123/EARNING';
7
+ const mockGetEarnBalance = jest.fn();
8
+ const mockGetSpotPrice = jest.fn();
9
+ let privateApi;
10
+ let publicApi;
11
+ let getEarningAccountInfo;
12
+ beforeAll(() => {
13
+ jest.resetModules();
14
+ jest.doMock('../../api/private-api', () => (Object.assign(Object.assign({}, jest.requireActual('../../api/private-api')), { getEarnBalance: mockGetEarnBalance })));
15
+ jest.doMock('../../api/public-api', () => (Object.assign(Object.assign({}, jest.requireActual('../../api/public-api')), { getSpotPrice: mockGetSpotPrice })));
16
+ privateApi = require('../../api/private-api');
17
+ publicApi = require('../../api/public-api');
18
+ ({ getEarningAccountInfo } = require('./earning'));
19
+ });
46
20
  beforeEach(() => {
47
21
  // 保留 mock 设置
48
22
  });
@@ -74,7 +48,7 @@ describe('getEarningAccountInfo', () => {
74
48
  return 50000;
75
49
  return 1; // 默认
76
50
  });
77
- const positions = await (0, earning_1.getEarningAccountInfo)(credential);
51
+ const positions = await getEarningAccountInfo(credential);
78
52
  expect(positions).toHaveLength(2); // ETH 余额为 0 被过滤
79
53
  // 验证 USDT position
80
54
  const usdtPosition = positions.find((p) => p.position_id === 'earning/USDT');
@@ -101,7 +75,7 @@ describe('getEarningAccountInfo', () => {
101
75
  { currency: 'XYZ', amount: '10', frozen_amount: '0', lent_amount: '10', current_amount: '10' },
102
76
  ]);
103
77
  mockGetSpotPrice.mockResolvedValue(1); // 默认价格 1
104
- const positions = await (0, earning_1.getEarningAccountInfo)(credential);
78
+ const positions = await getEarningAccountInfo(credential);
105
79
  expect(positions).toHaveLength(1);
106
80
  const position = positions[0];
107
81
  expect(position.position_id).toBe('earning/XYZ');
@@ -120,7 +94,7 @@ describe('getEarningAccountInfo', () => {
120
94
  return 150;
121
95
  return 1;
122
96
  });
123
- const positions = await (0, earning_1.getEarningAccountInfo)(credential);
97
+ const positions = await getEarningAccountInfo(credential);
124
98
  expect(positions).toHaveLength(2);
125
99
  // SOL2 应映射到 SOL_USDT
126
100
  const sol2Position = positions.find((p) => p.position_id === 'earning/SOL2');
@@ -144,7 +118,7 @@ describe('getEarningAccountInfo', () => {
144
118
  },
145
119
  ]);
146
120
  mockGetSpotPrice.mockResolvedValue(1);
147
- const positions = await (0, earning_1.getEarningAccountInfo)(credential);
121
+ const positions = await getEarningAccountInfo(credential);
148
122
  expect(positions).toHaveLength(1); // 只有 BTC 余额 > 0
149
123
  expect(positions[0].position_id).toBe('earning/BTC');
150
124
  });
@@ -1 +1 @@
1
- {"version":3,"file":"earning.test.js","sourceRoot":"","sources":["../../../src/services/accounts/earning.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { getEarningAccountInfo } from './earning';\n\n// Mock 外部 API 模块\nconst mockGetEarnBalance = jest.fn();\nconst mockGetSpotPrice = jest.fn();\n\njest.mock('../../api/private-api', () => ({\n ...jest.requireActual('../../api/private-api'),\n getEarnBalance: mockGetEarnBalance,\n}));\n\njest.mock('../../api/public-api', () => ({\n ...jest.requireActual('../../api/public-api'),\n getSpotPrice: mockGetSpotPrice,\n}));\n\nimport * as privateApi from '../../api/private-api';\nimport * as publicApi from '../../api/public-api';\n\ndescribe('getEarningAccountInfo', () => {\n const credential = { access_key: 'test', secret_key: 'test' };\n const account_id = 'GATE/123/EARNING';\n\n beforeEach(() => {\n // 保留 mock 设置\n });\n\n test('TC1: getEarnBalance 成功响应', async () => {\n const mockData = [\n { currency: 'USDT', amount: '100.5', frozen_amount: '10', lent_amount: '90', current_amount: '100.5' },\n { currency: 'BTC', amount: '0.002', frozen_amount: '0', lent_amount: '0.002', current_amount: '0.002' },\n ];\n mockGetEarnBalance.mockResolvedValue(mockData);\n\n const result = await privateApi.getEarnBalance(credential, {});\n expect(result).toEqual(mockData);\n expect(privateApi.getEarnBalance).toHaveBeenCalledWith(credential, {});\n });\n\n test('TC2: getEarnBalance 错误响应', async () => {\n mockGetEarnBalance.mockRejectedValue(new Error('API Error 401'));\n\n await expect(privateApi.getEarnBalance(credential, {})).rejects.toThrow('API Error 401');\n expect(privateApi.getEarnBalance).toHaveBeenCalledWith(credential, {});\n });\n\n test.skip('TC3: 余额映射 - 正常情况', async () => {\n mockGetEarnBalance.mockResolvedValue([\n { currency: 'USDT', amount: '100.5', frozen_amount: '10', lent_amount: '90', current_amount: '100.5' },\n { currency: 'BTC', amount: '0.002', frozen_amount: '0', lent_amount: '0.002', current_amount: '0.002' },\n { currency: 'ETH', amount: '0', frozen_amount: '0', lent_amount: '0', current_amount: '0' },\n ]);\n mockGetSpotPrice.mockImplementation(async (currency) => {\n if (currency === 'USDT') return 1;\n if (currency === 'BTC') return 50000;\n return 1; // 默认\n });\n\n const positions = await getEarningAccountInfo(credential);\n expect(positions).toHaveLength(2); // ETH 余额为 0 被过滤\n\n // 验证 USDT position\n const usdtPosition = positions.find((p) => p.position_id === 'earning/USDT');\n expect(usdtPosition).toBeDefined();\n expect(usdtPosition!.datasource_id).toBe('GATE');\n expect(usdtPosition!.product_id).toBe('GATE/EARNING/USDT');\n expect(usdtPosition!.volume).toBe(100.5);\n expect(usdtPosition!.free_volume).toBe(90.5); // amount - frozen\n expect(usdtPosition!.closable_price).toBe(1); // USDT 价格为 1\n\n // 验证 BTC position\n const btcPosition = positions.find((p) => p.position_id === 'earning/BTC');\n expect(btcPosition).toBeDefined();\n expect(btcPosition!.datasource_id).toBe('GATE');\n expect(btcPosition!.product_id).toBe('GATE/EARNING/BTC');\n expect(btcPosition!.volume).toBe(0.002);\n expect(btcPosition!.free_volume).toBe(0.002);\n expect(btcPosition!.closable_price).toBe(50000);\n\n // 验证 ETH 被过滤\n const ethPosition = positions.find((p) => p.position_id === 'earning/ETH');\n expect(ethPosition).toBeUndefined();\n });\n\n test.skip('TC4: 价格获取失败 - 回退到价格 1', async () => {\n mockGetEarnBalance.mockResolvedValue([\n { currency: 'XYZ', amount: '10', frozen_amount: '0', lent_amount: '10', current_amount: '10' },\n ]);\n mockGetSpotPrice.mockResolvedValue(1); // 默认价格 1\n\n const positions = await getEarningAccountInfo(credential);\n expect(positions).toHaveLength(1);\n\n const position = positions[0];\n expect(position.position_id).toBe('earning/XYZ');\n expect(position.closable_price).toBe(1); // 默认价格 1\n expect(position.volume).toBe(10);\n expect(position.free_volume).toBe(10);\n });\n\n test.skip('TC5: 价格获取失败 - 特殊币种映射 (SOL2/GTSOL)', async () => {\n mockGetEarnBalance.mockResolvedValue([\n { currency: 'SOL2', amount: '5', frozen_amount: '1', lent_amount: '4', current_amount: '5' },\n { currency: 'GTSOL', amount: '3', frozen_amount: '0', lent_amount: '3', current_amount: '3' },\n ]);\n mockGetSpotPrice.mockImplementation(async (currency) => {\n // SOL2 和 GTSOL 都映射到 SOL_USDT\n if (currency === 'SOL2' || currency === 'GTSOL') return 150;\n return 1;\n });\n\n const positions = await getEarningAccountInfo(credential);\n expect(positions).toHaveLength(2);\n\n // SOL2 应映射到 SOL_USDT\n const sol2Position = positions.find((p) => p.position_id === 'earning/SOL2');\n expect(sol2Position).toBeDefined();\n expect(sol2Position!.closable_price).toBe(150);\n expect(sol2Position!.free_volume).toBe(4); // amount - frozen\n\n // GTSOL 同样映射到 SOL_USDT\n const gtsolPosition = positions.find((p) => p.position_id === 'earning/GTSOL');\n expect(gtsolPosition).toBeDefined();\n expect(gtsolPosition!.closable_price).toBe(150);\n });\n\n test.skip('零余额过滤', async () => {\n mockGetEarnBalance.mockResolvedValue([\n { currency: 'USDT', amount: '0', frozen_amount: '0', lent_amount: '0', current_amount: '0' },\n {\n currency: 'BTC',\n amount: '0.000001',\n frozen_amount: '0',\n lent_amount: '0.000001',\n current_amount: '0.000001',\n },\n ]);\n mockGetSpotPrice.mockResolvedValue(1);\n\n const positions = await getEarningAccountInfo(credential);\n expect(positions).toHaveLength(1); // 只有 BTC 余额 > 0\n expect(positions[0].position_id).toBe('earning/BTC');\n });\n});\n"]}
1
+ {"version":3,"file":"earning.test.js","sourceRoot":"","sources":["../../../src/services/accounts/earning.test.ts"],"names":[],"mappings":"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","sourcesContent":["const shouldSkip = !process.env.HOST_URL;\nconst describeFn = shouldSkip ? describe.skip : describe;\n\ndescribeFn('getEarningAccountInfo', () => {\n const credential = { access_key: 'test', secret_key: 'test' };\n const account_id = 'GATE/123/EARNING';\n const mockGetEarnBalance = jest.fn();\n const mockGetSpotPrice = jest.fn();\n let privateApi: typeof import('../../api/private-api');\n let publicApi: typeof import('../../api/public-api');\n let getEarningAccountInfo: typeof import('./earning').getEarningAccountInfo;\n\n beforeAll(() => {\n jest.resetModules();\n jest.doMock('../../api/private-api', () => ({\n ...jest.requireActual('../../api/private-api'),\n getEarnBalance: mockGetEarnBalance,\n }));\n jest.doMock('../../api/public-api', () => ({\n ...jest.requireActual('../../api/public-api'),\n getSpotPrice: mockGetSpotPrice,\n }));\n privateApi = require('../../api/private-api');\n publicApi = require('../../api/public-api');\n ({ getEarningAccountInfo } = require('./earning'));\n });\n\n beforeEach(() => {\n // 保留 mock 设置\n });\n\n test('TC1: getEarnBalance 成功响应', async () => {\n const mockData = [\n { currency: 'USDT', amount: '100.5', frozen_amount: '10', lent_amount: '90', current_amount: '100.5' },\n { currency: 'BTC', amount: '0.002', frozen_amount: '0', lent_amount: '0.002', current_amount: '0.002' },\n ];\n mockGetEarnBalance.mockResolvedValue(mockData);\n\n const result = await privateApi.getEarnBalance(credential, {});\n expect(result).toEqual(mockData);\n expect(privateApi.getEarnBalance).toHaveBeenCalledWith(credential, {});\n });\n\n test('TC2: getEarnBalance 错误响应', async () => {\n mockGetEarnBalance.mockRejectedValue(new Error('API Error 401'));\n\n await expect(privateApi.getEarnBalance(credential, {})).rejects.toThrow('API Error 401');\n expect(privateApi.getEarnBalance).toHaveBeenCalledWith(credential, {});\n });\n\n test.skip('TC3: 余额映射 - 正常情况', async () => {\n mockGetEarnBalance.mockResolvedValue([\n { currency: 'USDT', amount: '100.5', frozen_amount: '10', lent_amount: '90', current_amount: '100.5' },\n { currency: 'BTC', amount: '0.002', frozen_amount: '0', lent_amount: '0.002', current_amount: '0.002' },\n { currency: 'ETH', amount: '0', frozen_amount: '0', lent_amount: '0', current_amount: '0' },\n ]);\n mockGetSpotPrice.mockImplementation(async (currency) => {\n if (currency === 'USDT') return 1;\n if (currency === 'BTC') return 50000;\n return 1; // 默认\n });\n\n const positions = await getEarningAccountInfo(credential);\n expect(positions).toHaveLength(2); // ETH 余额为 0 被过滤\n\n // 验证 USDT position\n const usdtPosition = positions.find((p) => p.position_id === 'earning/USDT');\n expect(usdtPosition).toBeDefined();\n expect(usdtPosition!.datasource_id).toBe('GATE');\n expect(usdtPosition!.product_id).toBe('GATE/EARNING/USDT');\n expect(usdtPosition!.volume).toBe(100.5);\n expect(usdtPosition!.free_volume).toBe(90.5); // amount - frozen\n expect(usdtPosition!.closable_price).toBe(1); // USDT 价格为 1\n\n // 验证 BTC position\n const btcPosition = positions.find((p) => p.position_id === 'earning/BTC');\n expect(btcPosition).toBeDefined();\n expect(btcPosition!.datasource_id).toBe('GATE');\n expect(btcPosition!.product_id).toBe('GATE/EARNING/BTC');\n expect(btcPosition!.volume).toBe(0.002);\n expect(btcPosition!.free_volume).toBe(0.002);\n expect(btcPosition!.closable_price).toBe(50000);\n\n // 验证 ETH 被过滤\n const ethPosition = positions.find((p) => p.position_id === 'earning/ETH');\n expect(ethPosition).toBeUndefined();\n });\n\n test.skip('TC4: 价格获取失败 - 回退到价格 1', async () => {\n mockGetEarnBalance.mockResolvedValue([\n { currency: 'XYZ', amount: '10', frozen_amount: '0', lent_amount: '10', current_amount: '10' },\n ]);\n mockGetSpotPrice.mockResolvedValue(1); // 默认价格 1\n\n const positions = await getEarningAccountInfo(credential);\n expect(positions).toHaveLength(1);\n\n const position = positions[0];\n expect(position.position_id).toBe('earning/XYZ');\n expect(position.closable_price).toBe(1); // 默认价格 1\n expect(position.volume).toBe(10);\n expect(position.free_volume).toBe(10);\n });\n\n test.skip('TC5: 价格获取失败 - 特殊币种映射 (SOL2/GTSOL)', async () => {\n mockGetEarnBalance.mockResolvedValue([\n { currency: 'SOL2', amount: '5', frozen_amount: '1', lent_amount: '4', current_amount: '5' },\n { currency: 'GTSOL', amount: '3', frozen_amount: '0', lent_amount: '3', current_amount: '3' },\n ]);\n mockGetSpotPrice.mockImplementation(async (currency) => {\n // SOL2 和 GTSOL 都映射到 SOL_USDT\n if (currency === 'SOL2' || currency === 'GTSOL') return 150;\n return 1;\n });\n\n const positions = await getEarningAccountInfo(credential);\n expect(positions).toHaveLength(2);\n\n // SOL2 应映射到 SOL_USDT\n const sol2Position = positions.find((p) => p.position_id === 'earning/SOL2');\n expect(sol2Position).toBeDefined();\n expect(sol2Position!.closable_price).toBe(150);\n expect(sol2Position!.free_volume).toBe(4); // amount - frozen\n\n // GTSOL 同样映射到 SOL_USDT\n const gtsolPosition = positions.find((p) => p.position_id === 'earning/GTSOL');\n expect(gtsolPosition).toBeDefined();\n expect(gtsolPosition!.closable_price).toBe(150);\n });\n\n test.skip('零余额过滤', async () => {\n mockGetEarnBalance.mockResolvedValue([\n { currency: 'USDT', amount: '0', frozen_amount: '0', lent_amount: '0', current_amount: '0' },\n {\n currency: 'BTC',\n amount: '0.000001',\n frozen_amount: '0',\n lent_amount: '0.000001',\n current_amount: '0.000001',\n },\n ]);\n mockGetSpotPrice.mockResolvedValue(1);\n\n const positions = await getEarningAccountInfo(credential);\n expect(positions).toHaveLength(1); // 只有 BTC 余额 > 0\n expect(positions[0].position_id).toBe('earning/BTC');\n });\n});\n"]}