@htlkg/data 0.0.21 → 0.0.23

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 (62) hide show
  1. package/dist/hooks/index.d.ts +702 -94
  2. package/dist/hooks/index.js +793 -56
  3. package/dist/hooks/index.js.map +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.js +802 -65
  6. package/dist/index.js.map +1 -1
  7. package/dist/mutations/index.js +4 -4
  8. package/dist/mutations/index.js.map +1 -1
  9. package/dist/queries/index.js +5 -5
  10. package/dist/queries/index.js.map +1 -1
  11. package/package.json +11 -12
  12. package/src/hooks/accounts/index.ts +2 -0
  13. package/src/hooks/{useAccounts.ts → accounts/useAccounts.ts} +48 -5
  14. package/src/hooks/accounts/usePaginatedAccounts.ts +166 -0
  15. package/src/hooks/brands/index.ts +2 -0
  16. package/src/hooks/{useBrands.ts → brands/useBrands.ts} +1 -1
  17. package/src/hooks/brands/usePaginatedBrands.ts +206 -0
  18. package/src/hooks/contacts/index.ts +2 -0
  19. package/src/hooks/contacts/useContacts.ts +176 -0
  20. package/src/hooks/contacts/usePaginatedContacts.ts +268 -0
  21. package/src/hooks/createPaginatedDataHook.ts +359 -0
  22. package/src/hooks/data-hook-errors.property.test.ts +4 -4
  23. package/src/hooks/data-hook-filters.property.test.ts +4 -4
  24. package/src/hooks/data-hooks.property.test.ts +4 -4
  25. package/src/hooks/index.ts +101 -8
  26. package/src/hooks/productInstances/index.ts +1 -0
  27. package/src/hooks/{useProductInstances.ts → productInstances/useProductInstances.ts} +9 -6
  28. package/src/hooks/products/index.ts +1 -0
  29. package/src/hooks/{useProducts.ts → products/useProducts.ts} +4 -5
  30. package/src/hooks/reservations/index.ts +2 -0
  31. package/src/hooks/reservations/usePaginatedReservations.ts +258 -0
  32. package/src/hooks/{useReservations.ts → reservations/useReservations.ts} +65 -10
  33. package/src/hooks/users/index.ts +2 -0
  34. package/src/hooks/users/usePaginatedUsers.ts +213 -0
  35. package/src/hooks/{useUsers.ts → users/useUsers.ts} +1 -1
  36. package/src/mutations/accounts/accounts.test.ts +287 -0
  37. package/src/mutations/{accounts.ts → accounts/accounts.ts} +2 -2
  38. package/src/mutations/accounts/index.ts +1 -0
  39. package/src/mutations/brands/brands.test.ts +292 -0
  40. package/src/mutations/{brands.ts → brands/brands.ts} +2 -2
  41. package/src/mutations/brands/index.ts +1 -0
  42. package/src/mutations/reservations/index.ts +1 -0
  43. package/src/mutations/{reservations.test.ts → reservations/reservations.test.ts} +1 -1
  44. package/src/mutations/{reservations.ts → reservations/reservations.ts} +2 -2
  45. package/src/mutations/users/index.ts +1 -0
  46. package/src/mutations/users/users.test.ts +289 -0
  47. package/src/mutations/{users.ts → users/users.ts} +2 -2
  48. package/src/queries/accounts/accounts.test.ts +228 -0
  49. package/src/queries/accounts/index.ts +1 -0
  50. package/src/queries/brands/brands.test.ts +288 -0
  51. package/src/queries/brands/index.ts +1 -0
  52. package/src/queries/products/index.ts +1 -0
  53. package/src/queries/products/products.test.ts +347 -0
  54. package/src/queries/reservations/index.ts +1 -0
  55. package/src/queries/users/index.ts +1 -0
  56. package/src/queries/users/users.test.ts +301 -0
  57. /package/src/queries/{accounts.ts → accounts/accounts.ts} +0 -0
  58. /package/src/queries/{brands.ts → brands/brands.ts} +0 -0
  59. /package/src/queries/{products.ts → products/products.ts} +0 -0
  60. /package/src/queries/{reservations.test.ts → reservations/reservations.test.ts} +0 -0
  61. /package/src/queries/{reservations.ts → reservations/reservations.ts} +0 -0
  62. /package/src/queries/{users.ts → users/users.ts} +0 -0
@@ -0,0 +1,289 @@
1
+ /**
2
+ * User Mutation Tests
3
+ *
4
+ * Tests for user CRUD operations including soft delete and restoration.
5
+ */
6
+
7
+ import { describe, it, expect, vi } from "vitest";
8
+ import {
9
+ createUser,
10
+ updateUser,
11
+ softDeleteUser,
12
+ restoreUser,
13
+ deleteUser,
14
+ } from "./users";
15
+
16
+ // Mock the systemSettings query functions
17
+ vi.mock("../../queries/systemSettings", () => ({
18
+ checkRestoreEligibility: vi.fn((deletedAt: string | null, retentionDays: number) => {
19
+ if (!deletedAt) {
20
+ return { canRestore: false, daysRemaining: 0, daysExpired: 0 };
21
+ }
22
+ const deletedDate = new Date(deletedAt);
23
+ const now = new Date();
24
+ const diffMs = now.getTime() - deletedDate.getTime();
25
+ const diffDays = diffMs / (1000 * 60 * 60 * 24);
26
+
27
+ if (diffDays > retentionDays) {
28
+ return {
29
+ canRestore: false,
30
+ daysRemaining: 0,
31
+ daysExpired: Math.floor(diffDays - retentionDays),
32
+ };
33
+ }
34
+
35
+ return {
36
+ canRestore: true,
37
+ daysRemaining: Math.ceil(retentionDays - diffDays),
38
+ daysExpired: 0,
39
+ };
40
+ }),
41
+ DEFAULT_SOFT_DELETE_RETENTION_DAYS: 30,
42
+ }));
43
+
44
+ describe("User Mutations", () => {
45
+ describe("createUser", () => {
46
+ const validInput = {
47
+ cognitoId: "cognito-123",
48
+ email: "user@example.com",
49
+ accountId: "account-123",
50
+ roles: ["BRAND_ADMIN"],
51
+ status: "active" as const,
52
+ };
53
+
54
+ it("should create a user with valid input", async () => {
55
+ const mockCreate = vi.fn().mockResolvedValue({
56
+ data: { id: "user-001", ...validInput },
57
+ errors: null,
58
+ });
59
+
60
+ const mockClient = {
61
+ models: {
62
+ User: { create: mockCreate },
63
+ },
64
+ };
65
+
66
+ const result = await createUser(mockClient, validInput);
67
+
68
+ expect(result).toBeTruthy();
69
+ expect(result?.id).toBe("user-001");
70
+ expect(mockCreate).toHaveBeenCalledWith(validInput);
71
+ });
72
+
73
+ it("should return null when GraphQL returns errors", async () => {
74
+ const mockCreate = vi.fn().mockResolvedValue({
75
+ data: null,
76
+ errors: [{ message: "Validation error" }],
77
+ });
78
+
79
+ const mockClient = {
80
+ models: {
81
+ User: { create: mockCreate },
82
+ },
83
+ };
84
+
85
+ const result = await createUser(mockClient, validInput);
86
+
87
+ expect(result).toBeNull();
88
+ });
89
+
90
+ it("should throw on unexpected error", async () => {
91
+ const mockCreate = vi.fn().mockRejectedValue(new Error("Network error"));
92
+
93
+ const mockClient = {
94
+ models: {
95
+ User: { create: mockCreate },
96
+ },
97
+ };
98
+
99
+ await expect(createUser(mockClient, validInput)).rejects.toThrow("Network error");
100
+ });
101
+ });
102
+
103
+ describe("updateUser", () => {
104
+ it("should update a user", async () => {
105
+ const mockUpdate = vi.fn().mockResolvedValue({
106
+ data: { id: "user-001", email: "updated@example.com" },
107
+ errors: null,
108
+ });
109
+
110
+ const mockClient = {
111
+ models: {
112
+ User: { update: mockUpdate },
113
+ },
114
+ };
115
+
116
+ const result = await updateUser(mockClient, {
117
+ id: "user-001",
118
+ email: "updated@example.com",
119
+ });
120
+
121
+ expect(result).toBeTruthy();
122
+ expect(result?.email).toBe("updated@example.com");
123
+ });
124
+
125
+ it("should return null on GraphQL error", async () => {
126
+ const mockUpdate = vi.fn().mockResolvedValue({
127
+ data: null,
128
+ errors: [{ message: "Update failed" }],
129
+ });
130
+
131
+ const mockClient = {
132
+ models: {
133
+ User: { update: mockUpdate },
134
+ },
135
+ };
136
+
137
+ const result = await updateUser(mockClient, { id: "user-001" });
138
+
139
+ expect(result).toBeNull();
140
+ });
141
+ });
142
+
143
+ describe("softDeleteUser", () => {
144
+ it("should set deletedAt and deletedBy", async () => {
145
+ const mockUpdate = vi.fn().mockResolvedValue({
146
+ data: { id: "user-001" },
147
+ errors: null,
148
+ });
149
+
150
+ const mockClient = {
151
+ models: {
152
+ User: { update: mockUpdate },
153
+ },
154
+ };
155
+
156
+ const result = await softDeleteUser(mockClient, "user-001", "admin@example.com");
157
+
158
+ expect(result).toBe(true);
159
+ const callArgs = mockUpdate.mock.calls[0][0];
160
+ expect(callArgs.id).toBe("user-001");
161
+ expect(callArgs.status).toBe("deleted");
162
+ expect(callArgs.deletedAt).toBeDefined();
163
+ expect(callArgs.deletedBy).toBe("admin@example.com");
164
+ });
165
+
166
+ it("should return false on GraphQL error", async () => {
167
+ const mockUpdate = vi.fn().mockResolvedValue({
168
+ data: null,
169
+ errors: [{ message: "Error" }],
170
+ });
171
+
172
+ const mockClient = {
173
+ models: {
174
+ User: { update: mockUpdate },
175
+ },
176
+ };
177
+
178
+ const result = await softDeleteUser(mockClient, "user-001", "admin@example.com");
179
+
180
+ expect(result).toBe(false);
181
+ });
182
+ });
183
+
184
+ describe("restoreUser", () => {
185
+ it("should restore a recently deleted user", async () => {
186
+ const recentDeletedAt = new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString();
187
+
188
+ const mockGet = vi.fn().mockResolvedValue({
189
+ data: { id: "user-001", deletedAt: recentDeletedAt },
190
+ errors: null,
191
+ });
192
+ const mockUpdate = vi.fn().mockResolvedValue({
193
+ data: { id: "user-001" },
194
+ errors: null,
195
+ });
196
+
197
+ const mockClient = {
198
+ models: {
199
+ User: { get: mockGet, update: mockUpdate },
200
+ },
201
+ };
202
+
203
+ const result = await restoreUser(mockClient, "user-001");
204
+
205
+ expect(result.success).toBe(true);
206
+ expect(mockUpdate).toHaveBeenCalledWith({
207
+ id: "user-001",
208
+ status: "active",
209
+ deletedAt: null,
210
+ deletedBy: null,
211
+ });
212
+ });
213
+
214
+ it("should reject restoration after retention period expires", async () => {
215
+ const oldDeletedAt = new Date(Date.now() - 60 * 24 * 60 * 60 * 1000).toISOString();
216
+
217
+ const mockGet = vi.fn().mockResolvedValue({
218
+ data: { id: "user-001", deletedAt: oldDeletedAt },
219
+ errors: null,
220
+ });
221
+
222
+ const mockClient = {
223
+ models: {
224
+ User: { get: mockGet, update: vi.fn() },
225
+ },
226
+ };
227
+
228
+ const result = await restoreUser(mockClient, "user-001", 30);
229
+
230
+ expect(result.success).toBe(false);
231
+ expect(result.error).toContain("Retention period");
232
+ });
233
+
234
+ it("should return error when user not found", async () => {
235
+ const mockGet = vi.fn().mockResolvedValue({
236
+ data: null,
237
+ errors: [{ message: "Not found" }],
238
+ });
239
+
240
+ const mockClient = {
241
+ models: {
242
+ User: { get: mockGet, update: vi.fn() },
243
+ },
244
+ };
245
+
246
+ const result = await restoreUser(mockClient, "nonexistent");
247
+
248
+ expect(result.success).toBe(false);
249
+ expect(result.error).toBe("User not found");
250
+ });
251
+ });
252
+
253
+ describe("deleteUser", () => {
254
+ it("should permanently delete a user", async () => {
255
+ const mockDelete = vi.fn().mockResolvedValue({
256
+ data: { id: "user-001" },
257
+ errors: null,
258
+ });
259
+
260
+ const mockClient = {
261
+ models: {
262
+ User: { delete: mockDelete },
263
+ },
264
+ };
265
+
266
+ const result = await deleteUser(mockClient, "user-001");
267
+
268
+ expect(result).toBe(true);
269
+ expect(mockDelete).toHaveBeenCalledWith({ id: "user-001" });
270
+ });
271
+
272
+ it("should return false on GraphQL error", async () => {
273
+ const mockDelete = vi.fn().mockResolvedValue({
274
+ data: null,
275
+ errors: [{ message: "Cannot delete" }],
276
+ });
277
+
278
+ const mockClient = {
279
+ models: {
280
+ User: { delete: mockDelete },
281
+ },
282
+ };
283
+
284
+ const result = await deleteUser(mockClient, "user-001");
285
+
286
+ expect(result).toBe(false);
287
+ });
288
+ });
289
+ });
@@ -8,8 +8,8 @@ import type { User } from "@htlkg/core/types";
8
8
  import {
9
9
  checkRestoreEligibility,
10
10
  DEFAULT_SOFT_DELETE_RETENTION_DAYS,
11
- } from "../queries/systemSettings";
12
- import type { CreateAuditFields, UpdateWithSoftDeleteFields } from "./common";
11
+ } from "../../queries/systemSettings";
12
+ import type { CreateAuditFields, UpdateWithSoftDeleteFields } from "../common";
13
13
 
14
14
  /**
15
15
  * Input type for creating a user
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Account Query Tests
3
+ *
4
+ * Tests for account query functions including filtering and relations.
5
+ */
6
+
7
+ import { describe, it, expect, vi } from "vitest";
8
+ import {
9
+ getAccount,
10
+ listAccounts,
11
+ getAccountWithBrands,
12
+ } from "./accounts";
13
+
14
+ describe("Account Queries", () => {
15
+ const mockAccount = {
16
+ id: "account-001",
17
+ name: "Test Account",
18
+ logo: "logo.png",
19
+ subscription: { plan: "premium" },
20
+ settings: {},
21
+ status: "active",
22
+ };
23
+
24
+ describe("getAccount", () => {
25
+ it("should return an account by ID", async () => {
26
+ const mockGet = vi.fn().mockResolvedValue({
27
+ data: mockAccount,
28
+ errors: null,
29
+ });
30
+
31
+ const mockClient = {
32
+ models: {
33
+ Account: { get: mockGet },
34
+ },
35
+ };
36
+
37
+ const result = await getAccount(mockClient, "account-001");
38
+
39
+ expect(result).toEqual(mockAccount);
40
+ expect(mockGet).toHaveBeenCalledWith({ id: "account-001" });
41
+ });
42
+
43
+ it("should return null when account not found", async () => {
44
+ const mockGet = vi.fn().mockResolvedValue({
45
+ data: null,
46
+ errors: [{ message: "Not found" }],
47
+ });
48
+
49
+ const mockClient = {
50
+ models: {
51
+ Account: { get: mockGet },
52
+ },
53
+ };
54
+
55
+ const result = await getAccount(mockClient, "nonexistent");
56
+
57
+ expect(result).toBeNull();
58
+ });
59
+
60
+ it("should throw on unexpected error", async () => {
61
+ const mockGet = vi.fn().mockRejectedValue(new Error("Network error"));
62
+
63
+ const mockClient = {
64
+ models: {
65
+ Account: { get: mockGet },
66
+ },
67
+ };
68
+
69
+ await expect(getAccount(mockClient, "account-001")).rejects.toThrow("Network error");
70
+ });
71
+ });
72
+
73
+ describe("listAccounts", () => {
74
+ const mockAccounts = [
75
+ mockAccount,
76
+ { ...mockAccount, id: "account-002", name: "Second Account" },
77
+ ];
78
+
79
+ it("should return a list of accounts", async () => {
80
+ const mockList = vi.fn().mockResolvedValue({
81
+ data: mockAccounts,
82
+ errors: null,
83
+ nextToken: null,
84
+ });
85
+
86
+ const mockClient = {
87
+ models: {
88
+ Account: { list: mockList },
89
+ },
90
+ };
91
+
92
+ const result = await listAccounts(mockClient);
93
+
94
+ expect(result.items).toHaveLength(2);
95
+ expect(result.items[0].id).toBe("account-001");
96
+ });
97
+
98
+ it("should pass filter options to the query", async () => {
99
+ const mockList = vi.fn().mockResolvedValue({
100
+ data: [mockAccount],
101
+ errors: null,
102
+ nextToken: null,
103
+ });
104
+
105
+ const mockClient = {
106
+ models: {
107
+ Account: { list: mockList },
108
+ },
109
+ };
110
+
111
+ const filter = { status: { eq: "active" } };
112
+ await listAccounts(mockClient, { filter, limit: 50 });
113
+
114
+ expect(mockList).toHaveBeenCalledWith({
115
+ filter,
116
+ limit: 50,
117
+ });
118
+ });
119
+
120
+ it("should return nextToken for pagination", async () => {
121
+ const mockList = vi.fn().mockResolvedValue({
122
+ data: [mockAccount],
123
+ errors: null,
124
+ nextToken: "token-123",
125
+ });
126
+
127
+ const mockClient = {
128
+ models: {
129
+ Account: { list: mockList },
130
+ },
131
+ };
132
+
133
+ const result = await listAccounts(mockClient, { limit: 1 });
134
+
135
+ expect(result.nextToken).toBe("token-123");
136
+ });
137
+
138
+ it("should return empty array on GraphQL error", async () => {
139
+ const mockList = vi.fn().mockResolvedValue({
140
+ data: null,
141
+ errors: [{ message: "Query failed" }],
142
+ nextToken: undefined,
143
+ });
144
+
145
+ const mockClient = {
146
+ models: {
147
+ Account: { list: mockList },
148
+ },
149
+ };
150
+
151
+ const result = await listAccounts(mockClient);
152
+
153
+ expect(result.items).toHaveLength(0);
154
+ expect(result.nextToken).toBeUndefined();
155
+ });
156
+ });
157
+
158
+ describe("getAccountWithBrands", () => {
159
+ it("should return account with brands", async () => {
160
+ const accountWithBrands = {
161
+ ...mockAccount,
162
+ brands: [
163
+ { id: "brand-001", name: "Brand One" },
164
+ { id: "brand-002", name: "Brand Two" },
165
+ ],
166
+ };
167
+
168
+ const mockGet = vi.fn().mockResolvedValue({
169
+ data: accountWithBrands,
170
+ errors: null,
171
+ });
172
+
173
+ const mockClient = {
174
+ models: {
175
+ Account: { get: mockGet },
176
+ },
177
+ };
178
+
179
+ const result = await getAccountWithBrands(mockClient, "account-001");
180
+
181
+ expect(result).toEqual(accountWithBrands);
182
+ expect(mockGet).toHaveBeenCalledWith(
183
+ { id: "account-001" },
184
+ {
185
+ selectionSet: [
186
+ "id",
187
+ "name",
188
+ "logo",
189
+ "subscription",
190
+ "settings",
191
+ "brands.*",
192
+ ],
193
+ }
194
+ );
195
+ });
196
+
197
+ it("should return null on error", async () => {
198
+ const mockGet = vi.fn().mockResolvedValue({
199
+ data: null,
200
+ errors: [{ message: "Not found" }],
201
+ });
202
+
203
+ const mockClient = {
204
+ models: {
205
+ Account: { get: mockGet },
206
+ },
207
+ };
208
+
209
+ const result = await getAccountWithBrands(mockClient, "nonexistent");
210
+
211
+ expect(result).toBeNull();
212
+ });
213
+
214
+ it("should throw on unexpected error", async () => {
215
+ const mockGet = vi.fn().mockRejectedValue(new Error("Database error"));
216
+
217
+ const mockClient = {
218
+ models: {
219
+ Account: { get: mockGet },
220
+ },
221
+ };
222
+
223
+ await expect(getAccountWithBrands(mockClient, "account-001")).rejects.toThrow(
224
+ "Database error"
225
+ );
226
+ });
227
+ });
228
+ });
@@ -0,0 +1 @@
1
+ export * from "./accounts";