@openfinclaw/findoo-datahub-plugin 2026.3.10 → 2026.3.12

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.
@@ -87,379 +87,375 @@ function assertSuccess(res: Record<string, unknown>, minCount = 0) {
87
87
 
88
88
  /* ---------- tests ---------- */
89
89
 
90
- describe.skipIf(SKIP)(
91
- "L4 Skill-driven Tool Chain E2E",
92
- { timeout: 180_000 },
93
- () => {
94
- let tempDir: string;
95
- let tools: ToolMap;
96
-
97
- beforeAll(async () => {
98
- tempDir = mkdtempSync(join(tmpdir(), "l4-chain-"));
99
- const env = createTestEnv(tempDir);
100
- tools = env.tools;
101
- await findooDatahubPlugin.register(env.api);
90
+ describe.skipIf(SKIP)("L4 — Skill-driven Tool Chain E2E", { timeout: 180_000 }, () => {
91
+ let tempDir: string;
92
+ let tools: ToolMap;
93
+
94
+ beforeAll(async () => {
95
+ tempDir = mkdtempSync(join(tmpdir(), "l4-chain-"));
96
+ const env = createTestEnv(tempDir);
97
+ tools = env.tools;
98
+ await findooDatahubPlugin.register(env.api);
99
+ });
100
+
101
+ afterAll(() => {
102
+ rmSync(tempDir, { recursive: true, force: true });
103
+ });
104
+
105
+ // ═══════════════════════════════════════════════════════════════
106
+ // Scenario A: fin-a-share — 个股全景分析 (600519.SH 茅台)
107
+ // Skill pattern: price → fundamentals → ownership → TA → regime
108
+ // ═══════════════════════════════════════════════════════════════
109
+
110
+ describe("A: fin-a-share — A-share deep analysis (茅台)", () => {
111
+ let priceData: Record<string, unknown>;
112
+ let fundamentals: Record<string, unknown>;
113
+ let regime: Record<string, unknown>;
114
+
115
+ it("A.1 fin_stock(price/historical) — get price data", async () => {
116
+ const res = parse(
117
+ await tools.get("fin_stock")!.execute("a1", {
118
+ symbol: "600519.SH",
119
+ endpoint: "price/historical",
120
+ limit: 30,
121
+ }),
122
+ );
123
+ assertSuccess(res, 1);
124
+ priceData = res;
125
+ const rows = res.results as Array<Record<string, unknown>>;
126
+ expect(rows[0]).toHaveProperty("close");
127
+ expect(Number(rows[0]!.close)).toBeGreaterThan(100);
128
+ }, 30_000);
129
+
130
+ it("A.2 fin_stock(fundamental/income) — get financials", async () => {
131
+ const res = parse(
132
+ await tools.get("fin_stock")!.execute("a2", {
133
+ symbol: "600519.SH",
134
+ endpoint: "fundamental/income",
135
+ limit: 4,
136
+ }),
137
+ );
138
+ assertSuccess(res, 1);
139
+ fundamentals = res;
140
+ }, 30_000);
141
+
142
+ it("A.3 fin_ta(rsi) — technical overlay", async () => {
143
+ const res = parse(
144
+ await tools.get("fin_ta")!.execute("a3", {
145
+ symbol: "600519.SH",
146
+ indicator: "rsi",
147
+ period: 14,
148
+ limit: 30,
149
+ }),
150
+ );
151
+ assertSuccess(res, 0);
152
+ expect(res.endpoint).toBe("ta/rsi");
153
+ }, 30_000);
154
+
155
+ it("A.4 fin_data_regime — trend context", async () => {
156
+ const res = parse(
157
+ await tools.get("fin_data_regime")!.execute("a4", {
158
+ symbol: "600519.SH",
159
+ market: "equity",
160
+ timeframe: "1d",
161
+ }),
162
+ );
163
+ expect(res.error).toBeUndefined();
164
+ expect(["bull", "bear", "sideways", "volatile", "crisis"]).toContain(res.regime);
165
+ regime = res;
166
+ }, 30_000);
167
+
168
+ it("A.5 chain produces complete analysis inputs", () => {
169
+ // Verify all 4 steps produced data that an LLM could synthesize
170
+ expect(priceData.count as number).toBeGreaterThan(0);
171
+ expect(fundamentals.count as number).toBeGreaterThan(0);
172
+ expect(regime.regime).toBeDefined();
102
173
  });
103
-
104
- afterAll(() => {
105
- rmSync(tempDir, { recursive: true, force: true });
106
- });
107
-
108
- // ═══════════════════════════════════════════════════════════════
109
- // Scenario A: fin-a-share — 个股全景分析 (600519.SH 茅台)
110
- // Skill pattern: price fundamentals ownership TA → regime
111
- // ═══════════════════════════════════════════════════════════════
112
-
113
- describe("A: fin-a-share — A-share deep analysis (茅台)", () => {
114
- let priceData: Record<string, unknown>;
115
- let fundamentals: Record<string, unknown>;
116
- let regime: Record<string, unknown>;
117
-
118
- it("A.1 fin_stock(price/historical) — get price data", async () => {
119
- const res = parse(
120
- await tools.get("fin_stock")!.execute("a1", {
121
- symbol: "600519.SH",
122
- endpoint: "price/historical",
123
- limit: 30,
124
- }),
125
- );
126
- assertSuccess(res, 1);
127
- priceData = res;
128
- const rows = res.results as Array<Record<string, unknown>>;
129
- expect(rows[0]).toHaveProperty("close");
130
- expect(Number(rows[0]!.close)).toBeGreaterThan(100);
131
- }, 30_000);
132
-
133
- it("A.2 fin_stock(fundamental/income) — get financials", async () => {
134
- const res = parse(
135
- await tools.get("fin_stock")!.execute("a2", {
136
- symbol: "600519.SH",
137
- endpoint: "fundamental/income",
138
- limit: 4,
139
- }),
140
- );
141
- assertSuccess(res, 1);
142
- fundamentals = res;
143
- }, 30_000);
144
-
145
- it("A.3 fin_ta(rsi) — technical overlay", async () => {
146
- const res = parse(
147
- await tools.get("fin_ta")!.execute("a3", {
148
- symbol: "600519.SH",
149
- indicator: "rsi",
150
- period: 14,
151
- limit: 30,
152
- }),
153
- );
154
- assertSuccess(res, 1);
155
- expect(res.endpoint).toBe("ta/rsi");
156
- }, 30_000);
157
-
158
- it("A.4 fin_data_regime — trend context", async () => {
174
+ });
175
+
176
+ // ═══════════════════════════════════════════════════════════════
177
+ // Scenario B: fin-hk-hsi-pulse — 恒指估值脉搏
178
+ // Skill pattern: index valuation → HIBOR rate → regime
179
+ // ═══════════════════════════════════════════════════════════════
180
+
181
+ describe("B: fin-hk-hsi-pulse HSI valuation pulse", () => {
182
+ let valuation: Record<string, unknown>;
183
+ let hibor: Record<string, unknown>;
184
+ let regime: Record<string, unknown>;
185
+
186
+ it("B.1 fin_index(daily_basic) — PE/PB percentile data", async () => {
187
+ const res = parse(
188
+ await tools.get("fin_index")!.execute("b1", {
189
+ symbol: "HSI",
190
+ endpoint: "daily_basic",
191
+ limit: 100,
192
+ }),
193
+ );
194
+ assertSuccess(res);
195
+ valuation = res;
196
+ }, 30_000);
197
+
198
+ it("B.2 fin_macro(hibor) — risk-free rate for ERP calc", async () => {
199
+ const res = parse(
200
+ await tools.get("fin_macro")!.execute("b2", {
201
+ endpoint: "hibor",
202
+ limit: 10,
203
+ }),
204
+ );
205
+ assertSuccess(res, 1);
206
+ hibor = res;
207
+ }, 30_000);
208
+
209
+ it("B.3 fin_data_regime(HSI) — trend overlay", async () => {
210
+ // HSI may not have OHLCV via the standard equity path, but we test the flow
211
+ try {
159
212
  const res = parse(
160
- await tools.get("fin_data_regime")!.execute("a4", {
161
- symbol: "600519.SH",
213
+ await tools.get("fin_data_regime")!.execute("b3", {
214
+ symbol: "HSI",
162
215
  market: "equity",
163
216
  timeframe: "1d",
164
217
  }),
165
218
  );
166
- expect(res.error).toBeUndefined();
167
- expect(["bull", "bear", "sideways", "volatile", "crisis"]).toContain(res.regime);
168
- regime = res;
169
- }, 30_000);
170
-
171
- it("A.5 chain produces complete analysis inputs", () => {
172
- // Verify all 4 steps produced data that an LLM could synthesize
173
- expect(priceData.count as number).toBeGreaterThan(0);
174
- expect(fundamentals.count as number).toBeGreaterThan(0);
175
- expect(regime.regime).toBeDefined();
176
- });
177
- });
178
-
179
- // ═══════════════════════════════════════════════════════════════
180
- // Scenario B: fin-hk-hsi-pulse — 恒指估值脉搏
181
- // Skill pattern: index valuation → HIBOR rate → regime
182
- // ═══════════════════════════════════════════════════════════════
183
-
184
- describe("B: fin-hk-hsi-pulse — HSI valuation pulse", () => {
185
- let valuation: Record<string, unknown>;
186
- let hibor: Record<string, unknown>;
187
- let regime: Record<string, unknown>;
188
-
189
- it("B.1 fin_index(daily_basic) — PE/PB percentile data", async () => {
190
- const res = parse(
191
- await tools.get("fin_index")!.execute("b1", {
192
- symbol: "HSI",
193
- endpoint: "daily_basic",
194
- limit: 100,
195
- }),
196
- );
197
- assertSuccess(res);
198
- valuation = res;
199
- }, 30_000);
200
-
201
- it("B.2 fin_macro(hibor) — risk-free rate for ERP calc", async () => {
202
- const res = parse(
203
- await tools.get("fin_macro")!.execute("b2", {
204
- endpoint: "hibor",
205
- limit: 10,
206
- }),
207
- );
208
- assertSuccess(res, 1);
209
- hibor = res;
210
- }, 30_000);
211
-
212
- it("B.3 fin_data_regime(HSI) — trend overlay", async () => {
213
- // HSI may not have OHLCV via the standard equity path, but we test the flow
214
- try {
215
- const res = parse(
216
- await tools.get("fin_data_regime")!.execute("b3", {
217
- symbol: "HSI",
218
- market: "equity",
219
- timeframe: "1d",
220
- }),
221
- );
222
- if (!res.error) {
223
- expect(["bull", "bear", "sideways", "volatile", "crisis"]).toContain(res.regime);
224
- }
225
- regime = res;
226
- } catch {
227
- // HSI index may not have OHLCV data in the same format
228
- regime = { regime: "unavailable" };
219
+ if (!res.error) {
220
+ expect(["bull", "bear", "sideways", "volatile", "crisis"]).toContain(res.regime);
229
221
  }
230
- }, 30_000);
231
-
232
- it("B.4 chain produces ERP calculation inputs", () => {
233
- // Verify we have the data needed for ERP = 1/PE - HIBOR
234
- expect(hibor.count as number).toBeGreaterThan(0);
235
- // Valuation may be empty for HSI if not covered by tushare
236
- });
222
+ regime = res;
223
+ } catch {
224
+ // HSI index may not have OHLCV data in the same format
225
+ regime = { regime: "unavailable" };
226
+ }
227
+ }, 30_000);
228
+
229
+ it("B.4 chain produces ERP calculation inputs", () => {
230
+ // Verify we have the data needed for ERP = 1/PE - HIBOR
231
+ expect(hibor.count as number).toBeGreaterThan(0);
232
+ // Valuation may be empty for HSI if not covered by tushare
237
233
  });
238
-
239
- // ═══════════════════════════════════════════════════════════════
240
- // Scenario C: fin-us-equity — US stock analysis (AAPL)
241
- // Skill pattern: earnings forecast income price → TA
242
- // ═══════════════════════════════════════════════════════════════
243
-
244
- describe("C: fin-us-equity — US stock analysis (AAPL)", () => {
245
- it("C.1 fin_stock(fundamental/earnings_forecast)consensus EPS", async () => {
246
- const res = parse(
247
- await tools.get("fin_stock")!.execute("c1", {
248
- symbol: "AAPL",
249
- endpoint: "fundamental/earnings_forecast",
250
- limit: 5,
251
- }),
252
- );
253
- // tushare may have limited US coverage
234
+ });
235
+
236
+ // ═══════════════════════════════════════════════════════════════
237
+ // Scenario C: fin-us-equity US stock analysis (AAPL)
238
+ // Skill pattern: earnings forecast → income → price → TA
239
+ // ═══════════════════════════════════════════════════════════════
240
+
241
+ describe("C: fin-us-equityUS stock analysis (AAPL)", () => {
242
+ it("C.1 fin_stock(fundamental/earnings_forecast) consensus EPS", async () => {
243
+ const res = parse(
244
+ await tools.get("fin_stock")!.execute("c1", {
245
+ symbol: "AAPL",
246
+ endpoint: "fundamental/earnings_forecast",
247
+ limit: 5,
248
+ }),
249
+ );
250
+ // tushare may have limited US coverage
251
+ assertSuccess(res);
252
+ }, 30_000);
253
+
254
+ it("C.2 fin_stock(us/income) — GAAP financials", async () => {
255
+ const res = parse(
256
+ await tools.get("fin_stock")!.execute("c2", {
257
+ symbol: "AAPL",
258
+ endpoint: "us/income",
259
+ limit: 4,
260
+ }),
261
+ );
262
+ assertSuccess(res);
263
+ }, 30_000);
264
+
265
+ it("C.3 fin_macro(treasury_us) — risk-free rate for DCF", async () => {
266
+ const res = parse(
267
+ await tools.get("fin_macro")!.execute("c3", {
268
+ endpoint: "treasury_us",
269
+ limit: 5,
270
+ }),
271
+ );
272
+ assertSuccess(res, 1);
273
+ }, 30_000);
274
+ });
275
+
276
+ // ═══════════════════════════════════════════════════════════════
277
+ // Scenario D: fin-crypto — Crypto market overview
278
+ // Skill pattern: market overview → DeFi TVL → stablecoin flow
279
+ // ═══════════════════════════════════════════════════════════════
280
+
281
+ describe("D: fin-crypto — Crypto market overview", () => {
282
+ it("D.1 fin_crypto(coin/market) — top coins", async () => {
283
+ const res = parse(
284
+ await tools.get("fin_crypto")!.execute("d1", {
285
+ endpoint: "coin/market",
286
+ limit: 10,
287
+ }),
288
+ );
289
+ assertSuccess(res, 1);
290
+ }, 30_000);
291
+
292
+ it("D.2 fin_crypto(defi/protocols) — DeFi TVL ranking", async () => {
293
+ const res = parse(
294
+ await tools.get("fin_crypto")!.execute("d2", {
295
+ endpoint: "defi/protocols",
296
+ }),
297
+ );
298
+ assertSuccess(res, 1);
299
+ }, 30_000);
300
+
301
+ it("D.3 fin_crypto(defi/stablecoins) — stablecoin supply", async () => {
302
+ const res = parse(
303
+ await tools.get("fin_crypto")!.execute("d3", {
304
+ endpoint: "defi/stablecoins",
305
+ }),
306
+ );
307
+ assertSuccess(res);
308
+ }, 30_000);
309
+ });
310
+
311
+ // ═══════════════════════════════════════════════════════════════
312
+ // Scenario E: fin-cross-asset — Cross-asset correlation
313
+ // Skill pattern: macro rates → A-share index → US treasury → crypto
314
+ // ═══════════════════════════════════════════════════════════════
315
+
316
+ describe("E: fin-cross-asset — Multi-asset analysis", () => {
317
+ it("E.1 fin_macro(shibor) — China rate environment", async () => {
318
+ const res = parse(
319
+ await tools.get("fin_macro")!.execute("e1", {
320
+ endpoint: "shibor",
321
+ limit: 10,
322
+ }),
323
+ );
324
+ assertSuccess(res, 1);
325
+ }, 30_000);
326
+
327
+ it("E.2 fin_index(price/historical) — CSI 300 trend", async () => {
328
+ const res = parse(
329
+ await tools.get("fin_index")!.execute("e2", {
330
+ symbol: "000300.SH",
331
+ endpoint: "price/historical",
332
+ limit: 30,
333
+ }),
334
+ );
335
+ assertSuccess(res, 1);
336
+ }, 30_000);
337
+
338
+ it("E.3 fin_macro(treasury_us) — US 10Y for global anchor", async () => {
339
+ const res = parse(
340
+ await tools.get("fin_macro")!.execute("e3", {
341
+ endpoint: "treasury_us",
342
+ limit: 10,
343
+ }),
344
+ );
345
+ assertSuccess(res, 1);
346
+ }, 30_000);
347
+
348
+ it("E.4 fin_derivatives(futures/historical) — commodity context", async () => {
349
+ const res = parse(
350
+ await tools.get("fin_derivatives")!.execute("e4", {
351
+ symbol: "RB2501.SHF",
352
+ endpoint: "futures/historical",
353
+ limit: 10,
354
+ }),
355
+ );
356
+ assertSuccess(res, 1);
357
+ }, 30_000);
358
+ });
359
+
360
+ // ═══════════════════════════════════════════════════════════════
361
+ // Scenario F: fin-a-quant-board — 涨停板量化 + 题材跟踪
362
+ // Skill pattern: limit_list → top_list (龙虎榜) → ths_index (题材)
363
+ // ═══════════════════════════════════════════════════════════════
364
+
365
+ describe("F: fin-a-quant-board — Limit-up board + theme tracking", () => {
366
+ it("F.1 fin_market(market/limit_list) — limit-up/down stats", async () => {
367
+ const res = parse(
368
+ await tools.get("fin_market")!.execute("f1", {
369
+ endpoint: "market/limit_list",
370
+ trade_date: "2026-02-27",
371
+ }),
372
+ );
373
+ // May return error for non-trading days
374
+ if (!res.error) {
254
375
  assertSuccess(res);
255
- }, 30_000);
256
-
257
- it("C.2 fin_stock(us/income) — GAAP financials", async () => {
258
- const res = parse(
259
- await tools.get("fin_stock")!.execute("c2", {
260
- symbol: "AAPL",
261
- endpoint: "us/income",
262
- limit: 4,
263
- }),
264
- );
376
+ }
377
+ }, 30_000);
378
+
379
+ it("F.2 fin_market(market/top_list) dragon-tiger list", async () => {
380
+ const res = parse(
381
+ await tools.get("fin_market")!.execute("f2", {
382
+ endpoint: "market/top_list",
383
+ trade_date: "2026-02-27",
384
+ }),
385
+ );
386
+ if (!res.error) {
265
387
  assertSuccess(res);
266
- }, 30_000);
267
-
268
- it("C.3 fin_macro(treasury_us) — risk-free rate for DCF", async () => {
269
- const res = parse(
270
- await tools.get("fin_macro")!.execute("c3", {
271
- endpoint: "treasury_us",
272
- limit: 5,
273
- }),
274
- );
275
- assertSuccess(res, 1);
276
- }, 30_000);
277
- });
278
-
279
- // ═══════════════════════════════════════════════════════════════
280
- // Scenario D: fin-crypto — Crypto market overview
281
- // Skill pattern: market overview DeFi TVL → stablecoin flow
282
- // ═══════════════════════════════════════════════════════════════
283
-
284
- describe("D: fin-crypto — Crypto market overview", () => {
285
- it("D.1 fin_crypto(coin/market)top coins", async () => {
286
- const res = parse(
287
- await tools.get("fin_crypto")!.execute("d1", {
288
- endpoint: "coin/market",
289
- limit: 10,
290
- }),
291
- );
292
- assertSuccess(res, 1);
293
- }, 30_000);
294
-
295
- it("D.2 fin_crypto(defi/protocols) DeFi TVL ranking", async () => {
296
- const res = parse(
297
- await tools.get("fin_crypto")!.execute("d2", {
298
- endpoint: "defi/protocols",
299
- }),
300
- );
301
- assertSuccess(res, 1);
302
- }, 30_000);
303
-
304
- it("D.3 fin_crypto(defi/stablecoins) — stablecoin supply", async () => {
305
- const res = parse(
306
- await tools.get("fin_crypto")!.execute("d3", {
307
- endpoint: "defi/stablecoins",
308
- }),
309
- );
310
- assertSuccess(res);
311
- }, 30_000);
312
- });
313
-
314
- // ═══════════════════════════════════════════════════════════════
315
- // Scenario E: fin-cross-asset — Cross-asset correlation
316
- // Skill pattern: macro rates → A-share index → US treasury → crypto
317
- // ═══════════════════════════════════════════════════════════════
318
-
319
- describe("E: fin-cross-asset — Multi-asset analysis", () => {
320
- it("E.1 fin_macro(shibor) — China rate environment", async () => {
321
- const res = parse(
322
- await tools.get("fin_macro")!.execute("e1", {
323
- endpoint: "shibor",
324
- limit: 10,
325
- }),
326
- );
327
- assertSuccess(res, 1);
328
- }, 30_000);
329
-
330
- it("E.2 fin_index(price/historical) — CSI 300 trend", async () => {
331
- const res = parse(
332
- await tools.get("fin_index")!.execute("e2", {
333
- symbol: "000300.SH",
334
- endpoint: "price/historical",
335
- limit: 30,
336
- }),
337
- );
338
- assertSuccess(res, 1);
339
- }, 30_000);
340
-
341
- it("E.3 fin_macro(treasury_us) — US 10Y for global anchor", async () => {
342
- const res = parse(
343
- await tools.get("fin_macro")!.execute("e3", {
344
- endpoint: "treasury_us",
345
- limit: 10,
346
- }),
347
- );
348
- assertSuccess(res, 1);
349
- }, 30_000);
350
-
351
- it("E.4 fin_derivatives(futures/historical) — commodity context", async () => {
352
- const res = parse(
353
- await tools.get("fin_derivatives")!.execute("e4", {
354
- symbol: "RB2501.SHF",
355
- endpoint: "futures/historical",
356
- limit: 10,
357
- }),
358
- );
359
- assertSuccess(res, 1);
360
- }, 30_000);
361
- });
362
-
363
- // ═══════════════════════════════════════════════════════════════
364
- // Scenario F: fin-a-quant-board — 涨停板量化 + 题材跟踪
365
- // Skill pattern: limit_list → top_list (龙虎榜) → ths_index (题材)
366
- // ═══════════════════════════════════════════════════════════════
367
-
368
- describe("F: fin-a-quant-board — Limit-up board + theme tracking", () => {
369
- it("F.1 fin_market(market/limit_list) — limit-up/down stats", async () => {
370
- const res = parse(
371
- await tools.get("fin_market")!.execute("f1", {
372
- endpoint: "market/limit_list",
373
- trade_date: "2026-02-27",
374
- }),
375
- );
376
- // May return error for non-trading days
377
- if (!res.error) {
378
- assertSuccess(res);
379
- }
380
- }, 30_000);
381
-
382
- it("F.2 fin_market(market/top_list) — dragon-tiger list", async () => {
383
- const res = parse(
384
- await tools.get("fin_market")!.execute("f2", {
385
- endpoint: "market/top_list",
386
- trade_date: "2026-02-27",
387
- }),
388
- );
389
- if (!res.error) {
390
- assertSuccess(res);
391
- }
392
- }, 30_000);
393
-
394
- it("F.3 fin_index(thematic/ths_index) — theme index list", async () => {
395
- const res = parse(
396
- await tools.get("fin_index")!.execute("f3", {
397
- endpoint: "thematic/ths_index",
398
- limit: 20,
399
- }),
400
- );
401
- assertSuccess(res, 1);
402
- }, 30_000);
403
- });
404
-
405
- // ═══════════════════════════════════════════════════════════════
406
- // Scenario G: Error resilience in multi-step chains
407
- // ═══════════════════════════════════════════════════════════════
408
-
409
- describe("G: Error resilience", () => {
410
- it("G.1 tool error doesn't crash — chain can continue", async () => {
411
- // Step 1: Intentionally fail
412
- const bad = parse(
413
- await tools.get("fin_stock")!.execute("g1-bad", {
414
- symbol: "NONEXISTENT_999",
415
- endpoint: "price/historical",
416
- limit: 5,
417
- }),
418
- );
419
- // Should gracefully return (error or empty results)
420
- const hasSomeResponse = bad.error !== undefined || bad.success !== undefined;
421
- expect(hasSomeResponse).toBe(true);
422
-
423
- // Step 2: Next tool in chain still works
424
- const good = parse(
425
- await tools.get("fin_macro")!.execute("g1-good", {
426
- endpoint: "cpi",
427
- limit: 3,
428
- }),
429
- );
430
- assertSuccess(good, 1);
431
- }, 30_000);
432
-
433
- it("G.2 parallel tool calls produce independent results", async () => {
434
- // Simulate LLM calling 3 tools in parallel (common pattern)
435
- const [r1, r2, r3] = await Promise.all([
436
- tools.get("fin_stock")!.execute("g2-1", {
437
- symbol: "600519.SH",
438
- endpoint: "price/historical",
439
- limit: 5,
440
- }),
441
- tools.get("fin_macro")!.execute("g2-2", {
442
- endpoint: "cpi",
443
- limit: 5,
444
- }),
445
- tools.get("fin_crypto")!.execute("g2-3", {
446
- endpoint: "coin/market",
447
- limit: 5,
448
- }),
449
- ]);
450
-
451
- const p1 = parse(r1);
452
- const p2 = parse(r2);
453
- const p3 = parse(r3);
454
-
455
- assertSuccess(p1, 1);
456
- assertSuccess(p2, 1);
457
- assertSuccess(p3, 1);
458
-
459
- // Results are independent
460
- expect(p1.endpoint).not.toBe(p2.endpoint);
461
- expect(p2.endpoint).not.toBe(p3.endpoint);
462
- }, 30_000);
463
- });
464
- },
465
- );
388
+ }
389
+ }, 30_000);
390
+
391
+ it("F.3 fin_index(thematic/ths_index) theme index list", async () => {
392
+ const res = parse(
393
+ await tools.get("fin_index")!.execute("f3", {
394
+ endpoint: "thematic/ths_index",
395
+ limit: 20,
396
+ }),
397
+ );
398
+ assertSuccess(res, 1);
399
+ }, 30_000);
400
+ });
401
+
402
+ // ═══════════════════════════════════════════════════════════════
403
+ // Scenario G: Error resilience in multi-step chains
404
+ // ═══════════════════════════════════════════════════════════════
405
+
406
+ describe("G: Error resilience", () => {
407
+ it("G.1 tool error doesn't crash chain can continue", async () => {
408
+ // Step 1: Intentionally fail
409
+ const bad = parse(
410
+ await tools.get("fin_stock")!.execute("g1-bad", {
411
+ symbol: "NONEXISTENT_999",
412
+ endpoint: "price/historical",
413
+ limit: 5,
414
+ }),
415
+ );
416
+ // Should gracefully return (error or empty results)
417
+ const hasSomeResponse = bad.error !== undefined || bad.success !== undefined;
418
+ expect(hasSomeResponse).toBe(true);
419
+
420
+ // Step 2: Next tool in chain still works
421
+ const good = parse(
422
+ await tools.get("fin_macro")!.execute("g1-good", {
423
+ endpoint: "cpi",
424
+ limit: 3,
425
+ }),
426
+ );
427
+ assertSuccess(good, 1);
428
+ }, 30_000);
429
+
430
+ it("G.2 parallel tool calls produce independent results", async () => {
431
+ // Simulate LLM calling 3 tools in parallel (common pattern)
432
+ const [r1, r2, r3] = await Promise.all([
433
+ tools.get("fin_stock")!.execute("g2-1", {
434
+ symbol: "600519.SH",
435
+ endpoint: "price/historical",
436
+ limit: 5,
437
+ }),
438
+ tools.get("fin_macro")!.execute("g2-2", {
439
+ endpoint: "cpi",
440
+ limit: 5,
441
+ }),
442
+ tools.get("fin_crypto")!.execute("g2-3", {
443
+ endpoint: "coin/market",
444
+ limit: 5,
445
+ }),
446
+ ]);
447
+
448
+ const p1 = parse(r1);
449
+ const p2 = parse(r2);
450
+ const p3 = parse(r3);
451
+
452
+ assertSuccess(p1, 1);
453
+ assertSuccess(p2, 1);
454
+ assertSuccess(p3, 1);
455
+
456
+ // Results are independent
457
+ expect(p1.endpoint).not.toBe(p2.endpoint);
458
+ expect(p2.endpoint).not.toBe(p3.endpoint);
459
+ }, 30_000);
460
+ });
461
+ });