@nimbleflux/fluxbase-sdk-react 2026.5.5-rc.2 → 2026.6.1
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.
- package/README.md +10 -17
- package/package.json +2 -2
- package/src/index.ts +43 -0
- package/src/test-utils.tsx +174 -0
- package/src/use-ai.test.ts +193 -0
- package/src/use-ai.ts +253 -0
- package/src/use-branches.test.ts +231 -0
- package/src/use-branches.ts +57 -1
- package/src/use-ddl.test.ts +205 -0
- package/src/use-ddl.ts +73 -0
- package/src/use-functions.test.ts +89 -0
- package/src/use-functions.ts +13 -1
- package/src/use-impersonation.test.ts +273 -0
- package/src/use-impersonation.ts +79 -0
- package/src/use-jobs.test.ts +224 -0
- package/src/use-jobs.ts +48 -0
- package/src/use-knowledge-base.test.ts +310 -0
- package/src/use-knowledge-base.ts +296 -0
- package/src/use-migrations.test.ts +280 -0
- package/src/use-migrations.ts +107 -0
- package/src/use-rpc.test.ts +147 -0
- package/src/use-rpc.ts +58 -0
- package/src/use-secrets.test.ts +253 -0
- package/src/use-secrets.ts +85 -0
- package/src/use-service-keys.test.ts +237 -0
- package/src/use-service-keys.ts +91 -0
- package/src/use-vector.test.ts +176 -0
- package/src/use-vector.ts +55 -0
package/README.md
CHANGED
|
@@ -135,18 +135,18 @@ function YourApp() {
|
|
|
135
135
|
|
|
136
136
|
## Documentation
|
|
137
137
|
|
|
138
|
-
📚 **[
|
|
138
|
+
📚 **[React SDK Reference](../../docs/src/content/docs/api/sdk-react/)**
|
|
139
139
|
|
|
140
140
|
### Core Guides
|
|
141
141
|
|
|
142
|
-
- **[
|
|
143
|
-
- **[
|
|
144
|
-
- **[
|
|
142
|
+
- **[SDK Reference](../../docs/src/content/docs/sdk/)** - Core SDK usage and configuration
|
|
143
|
+
- **[Admin Hooks Guide](./README-ADMIN.md)** - Comprehensive admin dashboard documentation
|
|
144
|
+
- **[Vector Search](../../docs/src/content/docs/guides/vector-search.md)** - Semantic search
|
|
145
145
|
|
|
146
146
|
### API Reference
|
|
147
147
|
|
|
148
|
-
- **[React Hooks API](../../docs/
|
|
149
|
-
- **[Core SDK API](../../docs/
|
|
148
|
+
- **[React Hooks API](../../docs/src/content/docs/api/sdk-react/)** - Auto-generated from source code
|
|
149
|
+
- **[Core SDK API](../../docs/src/content/docs/api/sdk/)** - Core TypeScript SDK reference
|
|
150
150
|
|
|
151
151
|
## TypeScript Support
|
|
152
152
|
|
|
@@ -169,18 +169,11 @@ function ProductList() {
|
|
|
169
169
|
|
|
170
170
|
## Examples
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
- React with Vite
|
|
175
|
-
- Next.js App Router
|
|
176
|
-
- Next.js Pages Router
|
|
177
|
-
- Authentication flows
|
|
178
|
-
- Realtime features
|
|
179
|
-
- File uploads
|
|
172
|
+
See `sdk-react/examples/` for a working example app.
|
|
180
173
|
|
|
181
174
|
## Contributing
|
|
182
175
|
|
|
183
|
-
Contributions are welcome! Please
|
|
176
|
+
Contributions are welcome! Please see the project repository for contribution guidelines.
|
|
184
177
|
|
|
185
178
|
## License
|
|
186
179
|
|
|
@@ -188,8 +181,8 @@ MIT © Fluxbase
|
|
|
188
181
|
|
|
189
182
|
## Links
|
|
190
183
|
|
|
191
|
-
- [Documentation](../../docs/docs/
|
|
192
|
-
- [API Reference](../../docs/
|
|
184
|
+
- [Documentation](../../docs/src/content/docs/sdk/)
|
|
185
|
+
- [API Reference](../../docs/src/content/docs/api/sdk-react/)
|
|
193
186
|
- [Core SDK](../sdk/)
|
|
194
187
|
- [GitHub](https://github.com/nimbleflux/fluxbase)
|
|
195
188
|
- [Issues](https://github.com/nimbleflux/fluxbase/issues)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nimbleflux/fluxbase-sdk-react",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.6.1",
|
|
4
4
|
"description": "React hooks for Fluxbase SDK",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@nimbleflux/fluxbase-sdk": "^2026.
|
|
42
|
+
"@nimbleflux/fluxbase-sdk": "^2026.6.1",
|
|
43
43
|
"@tanstack/react-query": "^5.96.2",
|
|
44
44
|
"react": "^18.0.0 || ^19.0.0"
|
|
45
45
|
},
|
package/src/index.ts
CHANGED
|
@@ -144,6 +144,49 @@ export { useSubmitJob, useJobStatus } from "./use-jobs";
|
|
|
144
144
|
// Branching hooks
|
|
145
145
|
export { useBranches } from "./use-branches";
|
|
146
146
|
|
|
147
|
+
// RPC hooks
|
|
148
|
+
export { useRPCList, useInvokeRPC } from "./use-rpc";
|
|
149
|
+
|
|
150
|
+
// Vector hooks
|
|
151
|
+
export { useVectorEmbed, useVectorSearch } from "./use-vector";
|
|
152
|
+
|
|
153
|
+
// Secrets hooks
|
|
154
|
+
export { useSecrets, useCreateSecret, useUpdateSecret, useDeleteSecret } from "./use-secrets";
|
|
155
|
+
|
|
156
|
+
// Service Keys hooks
|
|
157
|
+
export {
|
|
158
|
+
useServiceKeys,
|
|
159
|
+
useCreateServiceKey,
|
|
160
|
+
useRotateServiceKey,
|
|
161
|
+
useRevokeServiceKey,
|
|
162
|
+
} from "./use-service-keys";
|
|
163
|
+
|
|
164
|
+
// AI hooks
|
|
165
|
+
export {
|
|
166
|
+
useChatbots,
|
|
167
|
+
useConversations,
|
|
168
|
+
useConversation,
|
|
169
|
+
useDeleteConversation,
|
|
170
|
+
useAIChat,
|
|
171
|
+
type ChatMessage,
|
|
172
|
+
} from "./use-ai";
|
|
173
|
+
|
|
174
|
+
// Knowledge Base hooks
|
|
175
|
+
export {
|
|
176
|
+
useKnowledgeBases,
|
|
177
|
+
useKnowledgeBase,
|
|
178
|
+
useCreateKnowledgeBase,
|
|
179
|
+
useUpdateKnowledgeBase,
|
|
180
|
+
useDeleteKnowledgeBase,
|
|
181
|
+
useKBDocuments,
|
|
182
|
+
useAddDocument,
|
|
183
|
+
useUploadDocument,
|
|
184
|
+
useDeleteDocument,
|
|
185
|
+
useKBSearch,
|
|
186
|
+
useKBEntities,
|
|
187
|
+
useKnowledgeGraph,
|
|
188
|
+
} from "./use-knowledge-base";
|
|
189
|
+
|
|
147
190
|
// Re-export types from SDK
|
|
148
191
|
export type {
|
|
149
192
|
FluxbaseClient,
|
package/src/test-utils.tsx
CHANGED
|
@@ -91,6 +91,33 @@ export function createMockClient(
|
|
|
91
91
|
}),
|
|
92
92
|
...overrides.realtime,
|
|
93
93
|
},
|
|
94
|
+
jobs: {
|
|
95
|
+
submit: vi.fn().mockResolvedValue({ data: { id: "job-1", status: "pending" }, error: null }),
|
|
96
|
+
get: vi.fn().mockResolvedValue({ data: { id: "job-1", status: "completed" }, error: null }),
|
|
97
|
+
list: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
98
|
+
cancel: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
99
|
+
retry: vi.fn().mockResolvedValue({ data: { id: "job-2", status: "pending" }, error: null }),
|
|
100
|
+
getLogs: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
101
|
+
...overrides.jobs,
|
|
102
|
+
},
|
|
103
|
+
functions: {
|
|
104
|
+
invoke: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
105
|
+
list: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
106
|
+
get: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
107
|
+
...overrides.functions,
|
|
108
|
+
},
|
|
109
|
+
branching: {
|
|
110
|
+
list: vi.fn().mockResolvedValue({ data: { branches: [], total: 0, limit: 50, offset: 0 }, error: null }),
|
|
111
|
+
get: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
112
|
+
create: vi.fn().mockResolvedValue({ data: { id: "b1", slug: "test-branch", status: "creating" }, error: null }),
|
|
113
|
+
delete: vi.fn().mockResolvedValue({ error: null }),
|
|
114
|
+
reset: vi.fn().mockResolvedValue({ data: { id: "b1", slug: "test-branch", status: "creating" }, error: null }),
|
|
115
|
+
getActivity: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
116
|
+
getPoolStats: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
117
|
+
exists: vi.fn().mockResolvedValue(false),
|
|
118
|
+
waitForReady: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
119
|
+
...overrides.branching,
|
|
120
|
+
},
|
|
94
121
|
graphql: {
|
|
95
122
|
execute: vi.fn().mockResolvedValue({ data: null, errors: null }),
|
|
96
123
|
query: vi.fn().mockResolvedValue({ data: null, errors: null }),
|
|
@@ -142,12 +169,159 @@ export function createMockClient(
|
|
|
142
169
|
test: vi.fn().mockResolvedValue({}),
|
|
143
170
|
},
|
|
144
171
|
},
|
|
172
|
+
serviceKeys: {
|
|
173
|
+
list: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
174
|
+
get: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
175
|
+
create: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
176
|
+
update: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
177
|
+
delete: vi.fn().mockResolvedValue({ error: null }),
|
|
178
|
+
disable: vi.fn().mockResolvedValue({ error: null }),
|
|
179
|
+
enable: vi.fn().mockResolvedValue({ error: null }),
|
|
180
|
+
revoke: vi.fn().mockResolvedValue({ error: null }),
|
|
181
|
+
deprecate: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
182
|
+
rotate: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
183
|
+
getRevocationHistory: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
184
|
+
},
|
|
185
|
+
migrations: {
|
|
186
|
+
list: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
187
|
+
apply: vi
|
|
188
|
+
.fn()
|
|
189
|
+
.mockResolvedValue({ data: { message: "Migration applied" }, error: null }),
|
|
190
|
+
rollback: vi
|
|
191
|
+
.fn()
|
|
192
|
+
.mockResolvedValue({ data: { message: "Migration rolled back" }, error: null }),
|
|
193
|
+
sync: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
194
|
+
},
|
|
195
|
+
impersonation: {
|
|
196
|
+
impersonateUser: vi
|
|
197
|
+
.fn()
|
|
198
|
+
.mockResolvedValue({
|
|
199
|
+
session: null,
|
|
200
|
+
target_user: null,
|
|
201
|
+
access_token: "",
|
|
202
|
+
refresh_token: "",
|
|
203
|
+
expires_in: 0,
|
|
204
|
+
}),
|
|
205
|
+
impersonateAnon: vi
|
|
206
|
+
.fn()
|
|
207
|
+
.mockResolvedValue({
|
|
208
|
+
session: null,
|
|
209
|
+
target_user: null,
|
|
210
|
+
access_token: "",
|
|
211
|
+
refresh_token: "",
|
|
212
|
+
expires_in: 0,
|
|
213
|
+
}),
|
|
214
|
+
stop: vi
|
|
215
|
+
.fn()
|
|
216
|
+
.mockResolvedValue({ success: true, message: "Impersonation stopped" }),
|
|
217
|
+
getCurrent: vi.fn().mockResolvedValue({ session: null, target_user: null }),
|
|
218
|
+
listSessions: vi.fn().mockResolvedValue({ sessions: [], total: 0 }),
|
|
219
|
+
},
|
|
220
|
+
ddl: {
|
|
221
|
+
listSchemas: vi.fn().mockResolvedValue({ schemas: [] }),
|
|
222
|
+
createSchema: vi
|
|
223
|
+
.fn()
|
|
224
|
+
.mockResolvedValue({ message: "Schema created", schema: "" }),
|
|
225
|
+
listTables: vi.fn().mockResolvedValue({ tables: [] }),
|
|
226
|
+
deleteTable: vi
|
|
227
|
+
.fn()
|
|
228
|
+
.mockResolvedValue({ message: "Table deleted" }),
|
|
229
|
+
},
|
|
145
230
|
...overrides.admin,
|
|
146
231
|
},
|
|
232
|
+
rpc: Object.assign(
|
|
233
|
+
vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
234
|
+
{
|
|
235
|
+
list: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
236
|
+
invoke: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
237
|
+
getStatus: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
238
|
+
getLogs: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
239
|
+
waitForCompletion: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
240
|
+
},
|
|
241
|
+
),
|
|
242
|
+
secrets: {
|
|
243
|
+
list: vi.fn().mockResolvedValue([]),
|
|
244
|
+
create: vi.fn().mockResolvedValue({ id: "1", name: "test", scope: "global", version: 1, created_at: "", updated_at: "" }),
|
|
245
|
+
get: vi.fn().mockResolvedValue({ id: "1", name: "test", scope: "global", version: 1, created_at: "", updated_at: "" }),
|
|
246
|
+
update: vi.fn().mockResolvedValue({ id: "1", name: "test", scope: "global", version: 2, created_at: "", updated_at: "" }),
|
|
247
|
+
delete: vi.fn().mockResolvedValue(undefined),
|
|
248
|
+
getVersions: vi.fn().mockResolvedValue([]),
|
|
249
|
+
rollback: vi.fn().mockResolvedValue({ id: "1", name: "test", scope: "global", version: 3, created_at: "", updated_at: "" }),
|
|
250
|
+
stats: vi.fn().mockResolvedValue({ total: 0, expiring_soon: 0, expired: 0 }),
|
|
251
|
+
getById: vi.fn().mockResolvedValue({ id: "1", name: "test", scope: "global", version: 1, created_at: "", updated_at: "" }),
|
|
252
|
+
updateById: vi.fn().mockResolvedValue({ id: "1", name: "test", scope: "global", version: 2, created_at: "", updated_at: "" }),
|
|
253
|
+
deleteById: vi.fn().mockResolvedValue(undefined),
|
|
254
|
+
getVersionsById: vi.fn().mockResolvedValue([]),
|
|
255
|
+
rollbackById: vi.fn().mockResolvedValue({ id: "1", name: "test", scope: "global", version: 3, created_at: "", updated_at: "" }),
|
|
256
|
+
...overrides.secrets,
|
|
257
|
+
},
|
|
258
|
+
vector: {
|
|
259
|
+
embed: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
260
|
+
search: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
261
|
+
...overrides.vector,
|
|
262
|
+
},
|
|
147
263
|
...overrides,
|
|
148
264
|
} as unknown as FluxbaseClient;
|
|
149
265
|
}
|
|
150
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Create a mock client with AI/Knowledge Base support
|
|
269
|
+
*/
|
|
270
|
+
export function createMockAIClient(
|
|
271
|
+
overrides: Partial<FluxbaseClient> = {},
|
|
272
|
+
): FluxbaseClient {
|
|
273
|
+
const base = createMockClient(overrides);
|
|
274
|
+
return {
|
|
275
|
+
...base,
|
|
276
|
+
ai: {
|
|
277
|
+
listChatbots: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
278
|
+
getChatbot: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
279
|
+
lookupChatbot: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
280
|
+
createChat: vi.fn().mockReturnValue({
|
|
281
|
+
connect: vi.fn().mockResolvedValue(undefined),
|
|
282
|
+
disconnect: vi.fn(),
|
|
283
|
+
isConnected: vi.fn().mockReturnValue(true),
|
|
284
|
+
startChat: vi.fn().mockResolvedValue("conv-1"),
|
|
285
|
+
sendMessage: vi.fn().mockResolvedValue(undefined),
|
|
286
|
+
cancel: vi.fn(),
|
|
287
|
+
getAccumulatedContent: vi.fn().mockReturnValue(""),
|
|
288
|
+
}),
|
|
289
|
+
listConversations: vi
|
|
290
|
+
.fn()
|
|
291
|
+
.mockResolvedValue({ data: { conversations: [], total: 0, has_more: false }, error: null }),
|
|
292
|
+
getConversation: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
293
|
+
deleteConversation: vi.fn().mockResolvedValue({ error: null }),
|
|
294
|
+
updateConversation: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
295
|
+
...overrides.ai,
|
|
296
|
+
},
|
|
297
|
+
knowledgeBase: {
|
|
298
|
+
list: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
299
|
+
get: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
300
|
+
create: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
301
|
+
update: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
302
|
+
delete: vi.fn().mockResolvedValue({ data: true, error: null }),
|
|
303
|
+
listDocuments: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
304
|
+
getDocument: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
305
|
+
addDocument: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
306
|
+
uploadDocument: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
307
|
+
updateDocument: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
308
|
+
deleteDocument: vi.fn().mockResolvedValue({ data: true, error: null }),
|
|
309
|
+
deleteDocumentsByFilter: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
310
|
+
search: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
311
|
+
listEntities: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
312
|
+
searchEntities: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
313
|
+
getEntityRelationships: vi.fn().mockResolvedValue({ data: [], error: null }),
|
|
314
|
+
getKnowledgeGraph: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
315
|
+
...overrides.knowledgeBase,
|
|
316
|
+
},
|
|
317
|
+
vector: {
|
|
318
|
+
embed: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
319
|
+
search: vi.fn().mockResolvedValue({ data: null, error: null }),
|
|
320
|
+
...overrides.vector,
|
|
321
|
+
},
|
|
322
|
+
} as unknown as FluxbaseClient;
|
|
323
|
+
}
|
|
324
|
+
|
|
151
325
|
/**
|
|
152
326
|
* Create a fresh QueryClient for testing
|
|
153
327
|
*/
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for AI hooks
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
6
|
+
import { renderHook, waitFor, act } from "@testing-library/react";
|
|
7
|
+
import {
|
|
8
|
+
useChatbots,
|
|
9
|
+
useConversations,
|
|
10
|
+
useConversation,
|
|
11
|
+
useDeleteConversation,
|
|
12
|
+
} from "./use-ai";
|
|
13
|
+
import {
|
|
14
|
+
createMockAIClient,
|
|
15
|
+
createWrapper,
|
|
16
|
+
} from "./test-utils";
|
|
17
|
+
import type { FluxbaseClient } from "@nimbleflux/fluxbase-sdk";
|
|
18
|
+
|
|
19
|
+
describe("useChatbots", () => {
|
|
20
|
+
it("should list chatbots", async () => {
|
|
21
|
+
const mockChatbots = [
|
|
22
|
+
{ id: "1", name: "Bot1", namespace: "default", enabled: true, version: "1", source: "filesystem" },
|
|
23
|
+
];
|
|
24
|
+
const client = createMockAIClient({
|
|
25
|
+
ai: {
|
|
26
|
+
listChatbots: vi.fn().mockResolvedValue({ data: mockChatbots, error: null }),
|
|
27
|
+
} as any,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const { result } = renderHook(() => useChatbots(), {
|
|
31
|
+
wrapper: createWrapper(client),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
35
|
+
expect(result.current.data).toEqual(mockChatbots);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should filter by namespace", async () => {
|
|
39
|
+
const listChatbots = vi
|
|
40
|
+
.fn()
|
|
41
|
+
.mockResolvedValue({ data: [], error: null });
|
|
42
|
+
const client = createMockAIClient({
|
|
43
|
+
ai: { listChatbots } as any,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
renderHook(() => useChatbots("my-ns"), {
|
|
47
|
+
wrapper: createWrapper(client),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await waitFor(() => {
|
|
51
|
+
expect(listChatbots).toHaveBeenCalledWith("my-ns");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should handle errors", async () => {
|
|
56
|
+
const client = createMockAIClient({
|
|
57
|
+
ai: {
|
|
58
|
+
listChatbots: vi
|
|
59
|
+
.fn()
|
|
60
|
+
.mockResolvedValue({ data: null, error: new Error("Failed") }),
|
|
61
|
+
} as any,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const { result } = renderHook(() => useChatbots(), {
|
|
65
|
+
wrapper: createWrapper(client),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
69
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe("useConversations", () => {
|
|
74
|
+
it("should list conversations", async () => {
|
|
75
|
+
const mockData = {
|
|
76
|
+
conversations: [
|
|
77
|
+
{ id: "c1", chatbot: "bot1", namespace: "default", title: "Test", created_at: "", updated_at: "" },
|
|
78
|
+
],
|
|
79
|
+
total: 1,
|
|
80
|
+
has_more: false,
|
|
81
|
+
};
|
|
82
|
+
const client = createMockAIClient({
|
|
83
|
+
ai: {
|
|
84
|
+
listConversations: vi
|
|
85
|
+
.fn()
|
|
86
|
+
.mockResolvedValue({ data: mockData, error: null }),
|
|
87
|
+
} as any,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const { result } = renderHook(() => useConversations(), {
|
|
91
|
+
wrapper: createWrapper(client),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
95
|
+
expect(result.current.data).toEqual(mockData);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should pass options to listConversations", async () => {
|
|
99
|
+
const listConversations = vi
|
|
100
|
+
.fn()
|
|
101
|
+
.mockResolvedValue({ data: { conversations: [], total: 0, has_more: false }, error: null });
|
|
102
|
+
const client = createMockAIClient({
|
|
103
|
+
ai: { listConversations } as any,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
renderHook(
|
|
107
|
+
() => useConversations({ chatbot: "my-bot", limit: 10 }),
|
|
108
|
+
{ wrapper: createWrapper(client) },
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
await waitFor(() => {
|
|
112
|
+
expect(listConversations).toHaveBeenCalledWith({
|
|
113
|
+
chatbot: "my-bot",
|
|
114
|
+
limit: 10,
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("useConversation", () => {
|
|
121
|
+
it("should get a conversation by ID", async () => {
|
|
122
|
+
const mockConv = { id: "c1", chatbot: "bot1", namespace: "default", created_at: "", updated_at: "", messages: [] };
|
|
123
|
+
const client = createMockAIClient({
|
|
124
|
+
ai: {
|
|
125
|
+
getConversation: vi
|
|
126
|
+
.fn()
|
|
127
|
+
.mockResolvedValue({ data: mockConv, error: null }),
|
|
128
|
+
} as any,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const { result } = renderHook(() => useConversation("c1"), {
|
|
132
|
+
wrapper: createWrapper(client),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
136
|
+
expect(result.current.data).toEqual(mockConv);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should not fetch when conversationId is null", () => {
|
|
140
|
+
const client = createMockAIClient();
|
|
141
|
+
const { result } = renderHook(() => useConversation(null), {
|
|
142
|
+
wrapper: createWrapper(client),
|
|
143
|
+
});
|
|
144
|
+
expect(result.current.isLoading).toBe(false);
|
|
145
|
+
expect(result.current.data).toBeUndefined();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe("useDeleteConversation", () => {
|
|
150
|
+
it("should delete a conversation and invalidate queries", async () => {
|
|
151
|
+
const deleteConversation = vi
|
|
152
|
+
.fn()
|
|
153
|
+
.mockResolvedValue({ error: null });
|
|
154
|
+
const client = createMockAIClient({
|
|
155
|
+
ai: { deleteConversation } as any,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const { result } = renderHook(() => useDeleteConversation(), {
|
|
159
|
+
wrapper: createWrapper(client),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
await act(async () => {
|
|
163
|
+
await result.current.mutateAsync("conv-1");
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(deleteConversation).toHaveBeenCalledWith("conv-1");
|
|
167
|
+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("should handle errors", async () => {
|
|
171
|
+
const client = createMockAIClient({
|
|
172
|
+
ai: {
|
|
173
|
+
deleteConversation: vi
|
|
174
|
+
.fn()
|
|
175
|
+
.mockResolvedValue({ error: new Error("Not found") }),
|
|
176
|
+
} as any,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const { result } = renderHook(() => useDeleteConversation(), {
|
|
180
|
+
wrapper: createWrapper(client),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await act(async () => {
|
|
184
|
+
try {
|
|
185
|
+
await result.current.mutateAsync("conv-1");
|
|
186
|
+
} catch {
|
|
187
|
+
// expected
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await waitFor(() => expect(result.current.isError).toBe(true));
|
|
192
|
+
});
|
|
193
|
+
});
|