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