@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
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for DDL hooks
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi } from "vitest";
|
|
6
|
+
import { renderHook, waitFor, act } from "@testing-library/react";
|
|
7
|
+
import {
|
|
8
|
+
useSchemas,
|
|
9
|
+
useTables,
|
|
10
|
+
useCreateSchema,
|
|
11
|
+
useDeleteTable,
|
|
12
|
+
} from "./use-ddl";
|
|
13
|
+
import { createMockClient, createWrapper } from "./test-utils";
|
|
14
|
+
|
|
15
|
+
describe("useSchemas", () => {
|
|
16
|
+
it("should list schemas", async () => {
|
|
17
|
+
const mockSchemas = {
|
|
18
|
+
schemas: [
|
|
19
|
+
{ name: "public", owner: "postgres" },
|
|
20
|
+
{ name: "analytics", owner: "postgres" },
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
const client = createMockClient({
|
|
24
|
+
admin: {
|
|
25
|
+
ddl: {
|
|
26
|
+
listSchemas: vi.fn().mockResolvedValue(mockSchemas),
|
|
27
|
+
},
|
|
28
|
+
} as any,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const { result } = renderHook(() => useSchemas(), {
|
|
32
|
+
wrapper: createWrapper(client),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
36
|
+
expect(result.current.data).toEqual(mockSchemas);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should handle errors", async () => {
|
|
40
|
+
const client = createMockClient({
|
|
41
|
+
admin: {
|
|
42
|
+
ddl: {
|
|
43
|
+
listSchemas: vi.fn().mockRejectedValue(new Error("Failed")),
|
|
44
|
+
},
|
|
45
|
+
} as any,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const { result } = renderHook(() => useSchemas(), {
|
|
49
|
+
wrapper: createWrapper(client),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
53
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("useTables", () => {
|
|
58
|
+
it("should list all tables when no schema provided", async () => {
|
|
59
|
+
const mockTables = {
|
|
60
|
+
tables: [
|
|
61
|
+
{ schema: "public", name: "users" },
|
|
62
|
+
{ schema: "analytics", name: "events" },
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
const listTables = vi.fn().mockResolvedValue(mockTables);
|
|
66
|
+
const client = createMockClient({
|
|
67
|
+
admin: { ddl: { listTables } } as any,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const { result } = renderHook(() => useTables(), {
|
|
71
|
+
wrapper: createWrapper(client),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
75
|
+
expect(result.current.data).toEqual(mockTables);
|
|
76
|
+
expect(listTables).toHaveBeenCalledWith(undefined);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should list tables filtered by schema", async () => {
|
|
80
|
+
const mockTables = {
|
|
81
|
+
tables: [{ schema: "public", name: "users" }],
|
|
82
|
+
};
|
|
83
|
+
const listTables = vi.fn().mockResolvedValue(mockTables);
|
|
84
|
+
const client = createMockClient({
|
|
85
|
+
admin: { ddl: { listTables } } as any,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const { result } = renderHook(() => useTables("public"), {
|
|
89
|
+
wrapper: createWrapper(client),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
93
|
+
expect(listTables).toHaveBeenCalledWith("public");
|
|
94
|
+
expect(result.current.data).toEqual(mockTables);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should handle errors", async () => {
|
|
98
|
+
const client = createMockClient({
|
|
99
|
+
admin: {
|
|
100
|
+
ddl: {
|
|
101
|
+
listTables: vi.fn().mockRejectedValue(new Error("Failed")),
|
|
102
|
+
},
|
|
103
|
+
} as any,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const { result } = renderHook(() => useTables(), {
|
|
107
|
+
wrapper: createWrapper(client),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
111
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("useCreateSchema", () => {
|
|
116
|
+
it("should create a schema", async () => {
|
|
117
|
+
const mockResponse = { message: "Schema created", schema: "analytics" };
|
|
118
|
+
const createSchema = vi.fn().mockResolvedValue(mockResponse);
|
|
119
|
+
const client = createMockClient({
|
|
120
|
+
admin: { ddl: { createSchema } } as any,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const { result } = renderHook(() => useCreateSchema(), {
|
|
124
|
+
wrapper: createWrapper(client),
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await act(async () => {
|
|
128
|
+
await result.current.mutateAsync("analytics");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(createSchema).toHaveBeenCalledWith("analytics");
|
|
132
|
+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
|
133
|
+
expect(result.current.data).toEqual(mockResponse);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should handle errors", async () => {
|
|
137
|
+
const client = createMockClient({
|
|
138
|
+
admin: {
|
|
139
|
+
ddl: {
|
|
140
|
+
createSchema: vi.fn().mockRejectedValue(new Error("Schema exists")),
|
|
141
|
+
},
|
|
142
|
+
} as any,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const { result } = renderHook(() => useCreateSchema(), {
|
|
146
|
+
wrapper: createWrapper(client),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await act(async () => {
|
|
150
|
+
try {
|
|
151
|
+
await result.current.mutateAsync("analytics");
|
|
152
|
+
} catch {
|
|
153
|
+
// expected
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
await waitFor(() => expect(result.current.isError).toBe(true));
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("useDeleteTable", () => {
|
|
162
|
+
it("should delete a table", async () => {
|
|
163
|
+
const mockResponse = { message: "Table deleted" };
|
|
164
|
+
const deleteTable = vi.fn().mockResolvedValue(mockResponse);
|
|
165
|
+
const client = createMockClient({
|
|
166
|
+
admin: { ddl: { deleteTable } } as any,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const { result } = renderHook(() => useDeleteTable(), {
|
|
170
|
+
wrapper: createWrapper(client),
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
await act(async () => {
|
|
174
|
+
await result.current.mutateAsync({ schema: "public", name: "old_data" });
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
expect(deleteTable).toHaveBeenCalledWith("public", "old_data");
|
|
178
|
+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
|
179
|
+
expect(result.current.data).toEqual(mockResponse);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should handle errors", async () => {
|
|
183
|
+
const client = createMockClient({
|
|
184
|
+
admin: {
|
|
185
|
+
ddl: {
|
|
186
|
+
deleteTable: vi.fn().mockRejectedValue(new Error("Table not found")),
|
|
187
|
+
},
|
|
188
|
+
} as any,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const { result } = renderHook(() => useDeleteTable(), {
|
|
192
|
+
wrapper: createWrapper(client),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await act(async () => {
|
|
196
|
+
try {
|
|
197
|
+
await result.current.mutateAsync({ schema: "public", name: "missing" });
|
|
198
|
+
} catch {
|
|
199
|
+
// expected
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await waitFor(() => expect(result.current.isError).toBe(true));
|
|
204
|
+
});
|
|
205
|
+
});
|
package/src/use-ddl.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DDL hooks for Fluxbase React SDK
|
|
3
|
+
* Provides hooks for managing database schemas and tables
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
7
|
+
import { useFluxbaseClient } from "./context";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Hook to list all database schemas
|
|
11
|
+
*/
|
|
12
|
+
export function useSchemas() {
|
|
13
|
+
const client = useFluxbaseClient();
|
|
14
|
+
|
|
15
|
+
return useQuery({
|
|
16
|
+
queryKey: ["fluxbase", "schemas"],
|
|
17
|
+
queryFn: async () => {
|
|
18
|
+
return await client.admin.ddl.listSchemas();
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Hook to list tables, optionally filtered by schema
|
|
25
|
+
*/
|
|
26
|
+
export function useTables(schema?: string) {
|
|
27
|
+
const client = useFluxbaseClient();
|
|
28
|
+
|
|
29
|
+
return useQuery({
|
|
30
|
+
queryKey: ["fluxbase", "tables", schema],
|
|
31
|
+
queryFn: async () => {
|
|
32
|
+
return await client.admin.ddl.listTables(schema);
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Hook to create a new database schema
|
|
39
|
+
*/
|
|
40
|
+
export function useCreateSchema() {
|
|
41
|
+
const client = useFluxbaseClient();
|
|
42
|
+
const queryClient = useQueryClient();
|
|
43
|
+
|
|
44
|
+
return useMutation({
|
|
45
|
+
mutationFn: async (name: string) => {
|
|
46
|
+
return await client.admin.ddl.createSchema(name);
|
|
47
|
+
},
|
|
48
|
+
onSuccess: () => {
|
|
49
|
+
queryClient.invalidateQueries({
|
|
50
|
+
queryKey: ["fluxbase", "schemas"],
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Hook to delete a table from a schema
|
|
58
|
+
*/
|
|
59
|
+
export function useDeleteTable() {
|
|
60
|
+
const client = useFluxbaseClient();
|
|
61
|
+
const queryClient = useQueryClient();
|
|
62
|
+
|
|
63
|
+
return useMutation({
|
|
64
|
+
mutationFn: async ({ schema, name }: { schema: string; name: string }) => {
|
|
65
|
+
return await client.admin.ddl.deleteTable(schema, name);
|
|
66
|
+
},
|
|
67
|
+
onSuccess: () => {
|
|
68
|
+
queryClient.invalidateQueries({
|
|
69
|
+
queryKey: ["fluxbase", "tables"],
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Functions hooks
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi } from "vitest";
|
|
6
|
+
import { renderHook, waitFor, act } from "@testing-library/react";
|
|
7
|
+
import { useInvokeFunction, useFunctions } from "./use-functions";
|
|
8
|
+
import { createMockClient, createWrapper } from "./test-utils";
|
|
9
|
+
|
|
10
|
+
describe("useInvokeFunction", () => {
|
|
11
|
+
it("should invoke a function", async () => {
|
|
12
|
+
const invoke = vi.fn().mockResolvedValue({
|
|
13
|
+
data: { result: "ok" },
|
|
14
|
+
error: null,
|
|
15
|
+
});
|
|
16
|
+
const client = createMockClient({
|
|
17
|
+
functions: { invoke } as any,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const { result } = renderHook(() => useInvokeFunction(), {
|
|
21
|
+
wrapper: createWrapper(client),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await act(async () => {
|
|
25
|
+
await result.current.mutateAsync({ name: "hello", payload: { name: "World" } });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
expect(invoke).toHaveBeenCalledWith("hello", { body: { name: "World" }, method: undefined });
|
|
29
|
+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should handle errors", async () => {
|
|
33
|
+
const client = createMockClient({
|
|
34
|
+
functions: {
|
|
35
|
+
invoke: vi.fn().mockResolvedValue({ data: null, error: new Error("Failed") }),
|
|
36
|
+
} as any,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const { result } = renderHook(() => useInvokeFunction(), {
|
|
40
|
+
wrapper: createWrapper(client),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
await act(async () => {
|
|
44
|
+
try {
|
|
45
|
+
await result.current.mutateAsync({ name: "bad" });
|
|
46
|
+
} catch {
|
|
47
|
+
// expected
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await waitFor(() => expect(result.current.isError).toBe(true));
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("useFunctions", () => {
|
|
56
|
+
it("should list functions", async () => {
|
|
57
|
+
const mockFunctions = [
|
|
58
|
+
{ name: "hello", version: "1", namespace: "default" },
|
|
59
|
+
{ name: "world", version: "2", namespace: "default" },
|
|
60
|
+
];
|
|
61
|
+
const client = createMockClient({
|
|
62
|
+
functions: {
|
|
63
|
+
list: vi.fn().mockResolvedValue({ data: mockFunctions, error: null }),
|
|
64
|
+
} as any,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const { result } = renderHook(() => useFunctions(), {
|
|
68
|
+
wrapper: createWrapper(client),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
72
|
+
expect(result.current.data).toEqual(mockFunctions);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should handle errors", async () => {
|
|
76
|
+
const client = createMockClient({
|
|
77
|
+
functions: {
|
|
78
|
+
list: vi.fn().mockResolvedValue({ data: null, error: new Error("Failed") }),
|
|
79
|
+
} as any,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const { result } = renderHook(() => useFunctions(), {
|
|
83
|
+
wrapper: createWrapper(client),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
87
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
88
|
+
});
|
|
89
|
+
});
|
package/src/use-functions.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useMutation } from "@tanstack/react-query"
|
|
1
|
+
import { useMutation, useQuery } from "@tanstack/react-query"
|
|
2
2
|
import { useFluxbaseClient } from "./context"
|
|
3
3
|
|
|
4
4
|
export function useInvokeFunction() {
|
|
@@ -11,3 +11,15 @@ export function useInvokeFunction() {
|
|
|
11
11
|
},
|
|
12
12
|
})
|
|
13
13
|
}
|
|
14
|
+
|
|
15
|
+
export function useFunctions() {
|
|
16
|
+
const client = useFluxbaseClient()
|
|
17
|
+
return useQuery({
|
|
18
|
+
queryKey: ["fluxbase", "functions"],
|
|
19
|
+
queryFn: async (): Promise<unknown> => {
|
|
20
|
+
const { data, error } = await client.functions.list()
|
|
21
|
+
if (error) throw error
|
|
22
|
+
return data
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for impersonation hooks
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi } from "vitest";
|
|
6
|
+
import { renderHook, waitFor, act } from "@testing-library/react";
|
|
7
|
+
import {
|
|
8
|
+
useImpersonationSessions,
|
|
9
|
+
useCurrentImpersonation,
|
|
10
|
+
useImpersonateUser,
|
|
11
|
+
useStopImpersonation,
|
|
12
|
+
} from "./use-impersonation";
|
|
13
|
+
import { createMockClient, createWrapper } from "./test-utils";
|
|
14
|
+
|
|
15
|
+
describe("useImpersonationSessions", () => {
|
|
16
|
+
it("should list impersonation sessions", async () => {
|
|
17
|
+
const mockSessions = {
|
|
18
|
+
sessions: [
|
|
19
|
+
{
|
|
20
|
+
id: "s1",
|
|
21
|
+
admin_user_id: "admin-1",
|
|
22
|
+
target_user_id: "user-1",
|
|
23
|
+
impersonation_type: "user" as const,
|
|
24
|
+
target_role: "user",
|
|
25
|
+
reason: "Support",
|
|
26
|
+
started_at: "",
|
|
27
|
+
ended_at: null,
|
|
28
|
+
is_active: true,
|
|
29
|
+
ip_address: null,
|
|
30
|
+
user_agent: null,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
total: 1,
|
|
34
|
+
};
|
|
35
|
+
const client = createMockClient({
|
|
36
|
+
admin: {
|
|
37
|
+
impersonation: {
|
|
38
|
+
listSessions: vi.fn().mockResolvedValue(mockSessions),
|
|
39
|
+
},
|
|
40
|
+
} as any,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const { result } = renderHook(() => useImpersonationSessions(), {
|
|
44
|
+
wrapper: createWrapper(client),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
48
|
+
expect(result.current.data).toEqual(mockSessions);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should pass options to listSessions", async () => {
|
|
52
|
+
const listSessions = vi.fn().mockResolvedValue({ sessions: [], total: 0 });
|
|
53
|
+
const client = createMockClient({
|
|
54
|
+
admin: { impersonation: { listSessions } } as any,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
renderHook(() => useImpersonationSessions({ is_active: true }), {
|
|
58
|
+
wrapper: createWrapper(client),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await waitFor(() => {
|
|
62
|
+
expect(listSessions).toHaveBeenCalledWith({ is_active: true });
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should handle errors", async () => {
|
|
67
|
+
const client = createMockClient({
|
|
68
|
+
admin: {
|
|
69
|
+
impersonation: {
|
|
70
|
+
listSessions: vi.fn().mockRejectedValue(new Error("Failed")),
|
|
71
|
+
},
|
|
72
|
+
} as any,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const { result } = renderHook(() => useImpersonationSessions(), {
|
|
76
|
+
wrapper: createWrapper(client),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
80
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("useCurrentImpersonation", () => {
|
|
85
|
+
it("should get current impersonation session", async () => {
|
|
86
|
+
const mockCurrent = {
|
|
87
|
+
session: {
|
|
88
|
+
id: "s1",
|
|
89
|
+
admin_user_id: "admin-1",
|
|
90
|
+
target_user_id: "user-1",
|
|
91
|
+
impersonation_type: "user" as const,
|
|
92
|
+
target_role: "user",
|
|
93
|
+
reason: "Support",
|
|
94
|
+
started_at: "",
|
|
95
|
+
ended_at: null,
|
|
96
|
+
is_active: true,
|
|
97
|
+
ip_address: null,
|
|
98
|
+
user_agent: null,
|
|
99
|
+
},
|
|
100
|
+
target_user: { id: "user-1", email: "test@example.com", role: "user" },
|
|
101
|
+
};
|
|
102
|
+
const client = createMockClient({
|
|
103
|
+
admin: {
|
|
104
|
+
impersonation: {
|
|
105
|
+
getCurrent: vi.fn().mockResolvedValue(mockCurrent),
|
|
106
|
+
},
|
|
107
|
+
} as any,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const { result } = renderHook(() => useCurrentImpersonation(), {
|
|
111
|
+
wrapper: createWrapper(client),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
115
|
+
expect(result.current.data).toEqual(mockCurrent);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should return null session when not impersonating", async () => {
|
|
119
|
+
const client = createMockClient({
|
|
120
|
+
admin: {
|
|
121
|
+
impersonation: {
|
|
122
|
+
getCurrent: vi.fn().mockResolvedValue({
|
|
123
|
+
session: null,
|
|
124
|
+
target_user: null,
|
|
125
|
+
}),
|
|
126
|
+
},
|
|
127
|
+
} as any,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const { result } = renderHook(() => useCurrentImpersonation(), {
|
|
131
|
+
wrapper: createWrapper(client),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
135
|
+
expect(result.current.data?.session).toBeNull();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should handle errors", async () => {
|
|
139
|
+
const client = createMockClient({
|
|
140
|
+
admin: {
|
|
141
|
+
impersonation: {
|
|
142
|
+
getCurrent: vi.fn().mockRejectedValue(new Error("Failed")),
|
|
143
|
+
},
|
|
144
|
+
} as any,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const { result } = renderHook(() => useCurrentImpersonation(), {
|
|
148
|
+
wrapper: createWrapper(client),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
|
152
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe("useImpersonateUser", () => {
|
|
157
|
+
it("should impersonate a user", async () => {
|
|
158
|
+
const mockResponse = {
|
|
159
|
+
session: {
|
|
160
|
+
id: "s1",
|
|
161
|
+
admin_user_id: "admin-1",
|
|
162
|
+
target_user_id: "user-1",
|
|
163
|
+
impersonation_type: "user" as const,
|
|
164
|
+
target_role: "user",
|
|
165
|
+
reason: "Support",
|
|
166
|
+
started_at: "",
|
|
167
|
+
ended_at: null,
|
|
168
|
+
is_active: true,
|
|
169
|
+
ip_address: null,
|
|
170
|
+
user_agent: null,
|
|
171
|
+
},
|
|
172
|
+
target_user: { id: "user-1", email: "test@example.com", role: "user" },
|
|
173
|
+
access_token: "token-123",
|
|
174
|
+
refresh_token: "refresh-123",
|
|
175
|
+
expires_in: 3600,
|
|
176
|
+
};
|
|
177
|
+
const impersonateUser = vi.fn().mockResolvedValue(mockResponse);
|
|
178
|
+
const client = createMockClient({
|
|
179
|
+
admin: { impersonation: { impersonateUser } } as any,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const { result } = renderHook(() => useImpersonateUser(), {
|
|
183
|
+
wrapper: createWrapper(client),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await act(async () => {
|
|
187
|
+
await result.current.mutateAsync({
|
|
188
|
+
target_user_id: "user-1",
|
|
189
|
+
reason: "Support ticket #1234",
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(impersonateUser).toHaveBeenCalledWith({
|
|
194
|
+
target_user_id: "user-1",
|
|
195
|
+
reason: "Support ticket #1234",
|
|
196
|
+
});
|
|
197
|
+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
|
198
|
+
expect(result.current.data).toEqual(mockResponse);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("should handle errors", async () => {
|
|
202
|
+
const client = createMockClient({
|
|
203
|
+
admin: {
|
|
204
|
+
impersonation: {
|
|
205
|
+
impersonateUser: vi.fn().mockRejectedValue(new Error("Not allowed")),
|
|
206
|
+
},
|
|
207
|
+
} as any,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const { result } = renderHook(() => useImpersonateUser(), {
|
|
211
|
+
wrapper: createWrapper(client),
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
await act(async () => {
|
|
215
|
+
try {
|
|
216
|
+
await result.current.mutateAsync({
|
|
217
|
+
target_user_id: "user-1",
|
|
218
|
+
reason: "test",
|
|
219
|
+
});
|
|
220
|
+
} catch {
|
|
221
|
+
// expected
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
await waitFor(() => expect(result.current.isError).toBe(true));
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe("useStopImpersonation", () => {
|
|
230
|
+
it("should stop impersonation", async () => {
|
|
231
|
+
const mockResponse = { success: true, message: "Impersonation stopped" };
|
|
232
|
+
const stop = vi.fn().mockResolvedValue(mockResponse);
|
|
233
|
+
const client = createMockClient({
|
|
234
|
+
admin: { impersonation: { stop } } as any,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const { result } = renderHook(() => useStopImpersonation(), {
|
|
238
|
+
wrapper: createWrapper(client),
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
await act(async () => {
|
|
242
|
+
await result.current.mutateAsync();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
expect(stop).toHaveBeenCalled();
|
|
246
|
+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
|
247
|
+
expect(result.current.data).toEqual(mockResponse);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("should handle errors", async () => {
|
|
251
|
+
const client = createMockClient({
|
|
252
|
+
admin: {
|
|
253
|
+
impersonation: {
|
|
254
|
+
stop: vi.fn().mockRejectedValue(new Error("No active session")),
|
|
255
|
+
},
|
|
256
|
+
} as any,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const { result } = renderHook(() => useStopImpersonation(), {
|
|
260
|
+
wrapper: createWrapper(client),
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
await act(async () => {
|
|
264
|
+
try {
|
|
265
|
+
await result.current.mutateAsync();
|
|
266
|
+
} catch {
|
|
267
|
+
// expected
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
await waitFor(() => expect(result.current.isError).toBe(true));
|
|
272
|
+
});
|
|
273
|
+
});
|