@axiom-lattice/examples-deep_research 1.0.61 → 1.0.63

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.
@@ -0,0 +1,306 @@
1
+ type ToolExecutor = (input: any) => Promise<any>;
2
+
3
+ const registry = new Map<
4
+ string,
5
+ { config: { name: string; schema?: { parse: (v: any) => any } }; executor: ToolExecutor }
6
+ >();
7
+
8
+ jest.mock("@axiom-lattice/core", () => ({
9
+ registerToolLattice: (
10
+ key: string,
11
+ config: { name: string; schema?: { parse: (v: any) => any } },
12
+ executor: ToolExecutor
13
+ ) => {
14
+ registry.set(key, { config, executor });
15
+ },
16
+ getToolLattice: (key: string) => registry.get(key) ?? null,
17
+ }));
18
+
19
+ function callTool(key: string, input: any): Promise<any> {
20
+ const entry = registry.get(key);
21
+ if (!entry) throw new Error(`Tool "${key}" not registered`);
22
+ const parsed = entry.config.schema ? entry.config.schema.parse(input) : input;
23
+ return entry.executor(parsed);
24
+ }
25
+
26
+ // ============================================================
27
+ // sap_api_search
28
+ // ============================================================
29
+
30
+ describe("sap_api_search", () => {
31
+ beforeAll(async () => {
32
+ await import("../tools");
33
+ });
34
+
35
+ it("finds BusinessPartners by exact name", async () => {
36
+ const result = await callTool("sap_api_search", { query: "BusinessPartners" });
37
+ expect(result.totalMatches).toBeGreaterThanOrEqual(1);
38
+ expect(result.results.some((r: any) => r.name === "BusinessPartners")).toBe(true);
39
+ });
40
+
41
+ it("finds Items by exact name", async () => {
42
+ const result = await callTool("sap_api_search", { query: "Items" });
43
+ expect(result.results.some((r: any) => r.name === "Items")).toBe(true);
44
+ const items = result.results.find((r: any) => r.name === "Items");
45
+ expect(items.primaryKey).toBe("ItemCode");
46
+ expect(items.fields).toContain("ItemCode");
47
+ expect(items.fields).toContain("ItemName");
48
+ });
49
+
50
+ it("finds Orders by exact name", async () => {
51
+ const result = await callTool("sap_api_search", { query: "Orders" });
52
+ expect(result.results.some((r: any) => r.name === "Orders")).toBe(true);
53
+ });
54
+
55
+ it("finds PurchaseOrders by exact name", async () => {
56
+ const result = await callTool("sap_api_search", { query: "PurchaseOrders" });
57
+ expect(result.results.some((r: any) => r.name === "PurchaseOrders")).toBe(true);
58
+ });
59
+
60
+ it("finds Warehouses by exact name", async () => {
61
+ const result = await callTool("sap_api_search", { query: "Warehouses" });
62
+ expect(result.results.some((r: any) => r.name === "Warehouses")).toBe(true);
63
+ });
64
+
65
+ it("finds by partial/substring match", async () => {
66
+ const result = await callTool("sap_api_search", { query: "business" });
67
+ expect(result.totalMatches).toBeGreaterThan(1);
68
+ result.results.forEach((r: any) => {
69
+ expect(r.name.toLowerCase()).toContain("business");
70
+ });
71
+ });
72
+
73
+ it("finds by partial match — Item", async () => {
74
+ const result = await callTool("sap_api_search", { query: "Item" });
75
+ expect(result.totalMatches).toBeGreaterThan(1);
76
+ expect(result.results.some((r: any) => r.name === "Items")).toBe(true);
77
+ expect(result.results.some((r: any) => r.name === "ItemGroups")).toBe(true);
78
+ });
79
+
80
+ it("finds by partial match — Order", async () => {
81
+ const result = await callTool("sap_api_search", { query: "Order" });
82
+ expect(result.totalMatches).toBeGreaterThan(0);
83
+ expect(result.results.some((r: any) => r.name === "Orders")).toBe(true);
84
+ });
85
+
86
+ it("finds by partial match — Purchase", async () => {
87
+ const result = await callTool("sap_api_search", { query: "Purchase" });
88
+ expect(result.totalMatches).toBeGreaterThan(1);
89
+ });
90
+
91
+ it("finds by partial match — Inventory", async () => {
92
+ const result = await callTool("sap_api_search", { query: "Inventory" });
93
+ expect(result.totalMatches).toBeGreaterThan(1);
94
+ });
95
+
96
+ it("finds by Chinese keyword — 客户 (BP)", async () => {
97
+ const result = await callTool("sap_api_search", { query: "客户" });
98
+ expect(result.totalMatches).toBeGreaterThan(0);
99
+ result.results.forEach((r: any) => {
100
+ expect(r.domain).toBe("BusinessPartner");
101
+ });
102
+ });
103
+
104
+ it("finds by Chinese keyword — 物料 (Item)", async () => {
105
+ const result = await callTool("sap_api_search", { query: "物料" });
106
+ expect(result.totalMatches).toBeGreaterThan(0);
107
+ result.results.forEach((r: any) => {
108
+ expect(r.domain).toBe("Item / Product");
109
+ });
110
+ });
111
+
112
+ it("finds by Chinese keyword — 订单 (Document)", async () => {
113
+ const result = await callTool("sap_api_search", { query: "订单" });
114
+ expect(result.totalMatches).toBeGreaterThan(0);
115
+ result.results.forEach((r: any) => {
116
+ expect(r.domain).toBe("Document");
117
+ });
118
+ });
119
+
120
+ it("finds by Chinese keyword — 库存 (Inventory)", async () => {
121
+ const result = await callTool("sap_api_search", { query: "库存" });
122
+ expect(result.totalMatches).toBeGreaterThan(0);
123
+ result.results.forEach((r: any) => {
124
+ expect(r.domain).toBe("Inventory / Warehouse");
125
+ });
126
+ });
127
+
128
+ it("filters by domain", async () => {
129
+ const result = await callTool("sap_api_search", {
130
+ query: "",
131
+ domain: "BusinessPartner",
132
+ });
133
+ expect(result.totalMatches).toBeGreaterThan(0);
134
+ result.results.forEach((r: any) => {
135
+ expect(r.domain).toBe("BusinessPartner");
136
+ });
137
+ });
138
+
139
+ it("filters by domain — Item / Product", async () => {
140
+ const result = await callTool("sap_api_search", {
141
+ query: "",
142
+ domain: "Item / Product",
143
+ });
144
+ expect(result.totalMatches).toBeGreaterThan(0);
145
+ result.results.forEach((r: any) => {
146
+ expect(r.domain).toBe("Item / Product");
147
+ });
148
+ });
149
+
150
+ it("filters by domain — Inventory / Warehouse", async () => {
151
+ const result = await callTool("sap_api_search", {
152
+ query: "",
153
+ domain: "Inventory / Warehouse",
154
+ });
155
+ expect(result.totalMatches).toBeGreaterThan(0);
156
+ result.results.forEach((r: any) => {
157
+ expect(r.domain).toBe("Inventory / Warehouse");
158
+ });
159
+ });
160
+
161
+ it("returns EntitySet with primaryKey in results", async () => {
162
+ const result = await callTool("sap_api_search", { query: "BusinessPartners" });
163
+ const bp = result.results.find((r: any) => r.name === "BusinessPartners");
164
+ expect(bp.primaryKey).toBe("CardCode");
165
+ expect(bp.kind).toBe("EntitySet");
166
+ });
167
+
168
+ it("returns FunctionImport in results", async () => {
169
+ const result = await callTool("sap_api_search", {
170
+ query: "InitData",
171
+ maxResults: 20,
172
+ });
173
+ expect(result.results.some((r: any) => r.kind === "FunctionImport")).toBe(true);
174
+ });
175
+
176
+ it("returns suggestion when no results found", async () => {
177
+ const result = await callTool("sap_api_search", { query: "xyzwqkxyz" });
178
+ expect(result.totalMatches).toBe(0);
179
+ expect(result.results).toHaveLength(0);
180
+ expect(result.suggestion).toBeDefined();
181
+ });
182
+
183
+ it("respects maxResults limit", async () => {
184
+ const result = await callTool("sap_api_search", { query: "", maxResults: 3 });
185
+ expect(result.results.length).toBeLessThanOrEqual(3);
186
+ });
187
+
188
+ it("returns domain distribution", async () => {
189
+ const result = await callTool("sap_api_search", { query: "" });
190
+ expect(typeof result.domainsFound).toBe("object");
191
+ if (result.results.length > 0) {
192
+ const total = Object.values(result.domainsFound).reduce(
193
+ (s: number, v: any) => s + v,
194
+ 0
195
+ ) as number;
196
+ expect(total).toBe(result.results.length);
197
+ }
198
+ });
199
+
200
+ it("returns hint in each result", async () => {
201
+ const result = await callTool("sap_api_search", { query: "BusinessPartners" });
202
+ expect(result.results[0].hint).toBeDefined();
203
+ expect(result.results[0].hint).toContain("sap_api_call");
204
+ });
205
+ });
206
+
207
+ // ============================================================
208
+ // sap_api_call
209
+ // ============================================================
210
+
211
+ describe("sap_api_call", () => {
212
+ beforeAll(async () => {
213
+ await import("../tools");
214
+ });
215
+
216
+ it("returns HTTP response with unified format", async () => {
217
+ const result = await callTool("sap_api_call", {
218
+ entitySet: "BusinessPartners",
219
+ method: "GET",
220
+ queryOptions: "$top=1&$select=CardCode,CardName",
221
+ });
222
+
223
+ expect(result.ok).toBe(true);
224
+ expect(result.status).toBe(200);
225
+ expect(result.data).toBeDefined();
226
+ expect(result.data.value).toBeDefined();
227
+ expect(result.data.value.length).toBeLessThanOrEqual(1);
228
+ });
229
+
230
+ it("passes filter query correctly", async () => {
231
+ const result = await callTool("sap_api_call", {
232
+ entitySet: "BusinessPartners",
233
+ method: "GET",
234
+ queryOptions: "$top=1&$select=CardCode,CardName",
235
+ });
236
+
237
+ const bp = result.data.value[0];
238
+ expect(bp.CardCode).toBeDefined();
239
+ expect(bp.CardName).toBeDefined();
240
+ });
241
+
242
+ it("returns 200 for Items query", async () => {
243
+ const result = await callTool("sap_api_call", {
244
+ entitySet: "Items",
245
+ method: "GET",
246
+ queryOptions: "$top=1&$select=ItemCode,ItemName",
247
+ });
248
+
249
+ expect(result.ok).toBe(true);
250
+ expect(result.status).toBe(200);
251
+ expect(result.data.value[0].ItemCode).toBeDefined();
252
+ });
253
+
254
+ it("returns 200 for Orders query", async () => {
255
+ const result = await callTool("sap_api_call", {
256
+ entitySet: "Orders",
257
+ method: "GET",
258
+ queryOptions: "$top=1&$select=DocEntry,DocTotal",
259
+ });
260
+
261
+ expect(result.ok).toBe(true);
262
+ expect(result.data.value[0].DocEntry).toBeDefined();
263
+ });
264
+
265
+ it("returns 200 for Warehouses query", async () => {
266
+ const result = await callTool("sap_api_call", {
267
+ entitySet: "Warehouses",
268
+ method: "GET",
269
+ queryOptions: "$top=1&$select=WarehouseCode,WarehouseName",
270
+ });
271
+
272
+ expect(result.ok).toBe(true);
273
+ expect(result.data.value[0].WarehouseCode).toBeDefined();
274
+ });
275
+
276
+ it("returns 200 for InventoryGenEntries query", async () => {
277
+ const result = await callTool("sap_api_call", {
278
+ entitySet: "InventoryGenEntries",
279
+ method: "GET",
280
+ queryOptions: "$top=1&$select=DocEntry,DocDate",
281
+ });
282
+
283
+ expect(result.ok).toBe(true);
284
+ });
285
+
286
+ it("respects $top limit", async () => {
287
+ const result = await callTool("sap_api_call", {
288
+ entitySet: "BusinessPartners",
289
+ method: "GET",
290
+ queryOptions: "$top=3&$select=CardCode",
291
+ });
292
+
293
+ expect(result.data.value.length).toBeLessThanOrEqual(3);
294
+ });
295
+
296
+ it("returns error for POST without auth", async () => {
297
+ const result = await callTool("sap_api_call", {
298
+ entitySet: "BusinessPartners",
299
+ method: "POST",
300
+ body: { CardCode: "TEST001", CardName: "Test" },
301
+ });
302
+
303
+ expect(result.ok).toBe(false);
304
+ expect(result.error).toBeDefined();
305
+ });
306
+ });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * SAP B1 Service Layer API Tools
3
+ *
4
+ * 提供 2 个工具用于搜索和调用 SAP Business One Service Layer 的 OData API:
5
+ * sap_api_search — 搜索接口元数据
6
+ * sap_api_call — 构造 API 调用
7
+ */
8
+ import "./tools";