@rebasepro/server-postgresql 0.0.1-canary.4d4fb3e
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/LICENSE +6 -0
- package/README.md +106 -0
- package/build-errors.txt +37 -0
- package/dist/common/src/collections/CollectionRegistry.d.ts +48 -0
- package/dist/common/src/collections/index.d.ts +1 -0
- package/dist/common/src/data/buildRebaseData.d.ts +14 -0
- package/dist/common/src/index.d.ts +3 -0
- package/dist/common/src/util/builders.d.ts +57 -0
- package/dist/common/src/util/callbacks.d.ts +6 -0
- package/dist/common/src/util/collections.d.ts +11 -0
- package/dist/common/src/util/common.d.ts +2 -0
- package/dist/common/src/util/conditions.d.ts +26 -0
- package/dist/common/src/util/entities.d.ts +36 -0
- package/dist/common/src/util/enums.d.ts +3 -0
- package/dist/common/src/util/index.d.ts +16 -0
- package/dist/common/src/util/navigation_from_path.d.ts +34 -0
- package/dist/common/src/util/navigation_utils.d.ts +20 -0
- package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
- package/dist/common/src/util/paths.d.ts +14 -0
- package/dist/common/src/util/permissions.d.ts +5 -0
- package/dist/common/src/util/references.d.ts +2 -0
- package/dist/common/src/util/relations.d.ts +12 -0
- package/dist/common/src/util/resolutions.d.ts +72 -0
- package/dist/common/src/util/storage.d.ts +24 -0
- package/dist/index.es.js +10635 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +10643 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +112 -0
- package/dist/server-postgresql/src/PostgresBootstrapper.d.ts +40 -0
- package/dist/server-postgresql/src/auth/ensure-tables.d.ts +6 -0
- package/dist/server-postgresql/src/auth/services.d.ts +188 -0
- package/dist/server-postgresql/src/cli.d.ts +1 -0
- package/dist/server-postgresql/src/collections/PostgresCollectionRegistry.d.ts +43 -0
- package/dist/server-postgresql/src/connection.d.ts +7 -0
- package/dist/server-postgresql/src/data-transformer.d.ts +36 -0
- package/dist/server-postgresql/src/databasePoolManager.d.ts +20 -0
- package/dist/server-postgresql/src/history/HistoryService.d.ts +71 -0
- package/dist/server-postgresql/src/history/ensure-history-table.d.ts +7 -0
- package/dist/server-postgresql/src/index.d.ts +13 -0
- package/dist/server-postgresql/src/interfaces.d.ts +18 -0
- package/dist/server-postgresql/src/schema/auth-schema.d.ts +767 -0
- package/dist/server-postgresql/src/schema/generate-drizzle-schema-logic.d.ts +2 -0
- package/dist/server-postgresql/src/schema/generate-drizzle-schema.d.ts +1 -0
- package/dist/server-postgresql/src/services/BranchService.d.ts +47 -0
- package/dist/server-postgresql/src/services/EntityFetchService.d.ts +195 -0
- package/dist/server-postgresql/src/services/EntityPersistService.d.ts +41 -0
- package/dist/server-postgresql/src/services/RelationService.d.ts +92 -0
- package/dist/server-postgresql/src/services/entity-helpers.d.ts +24 -0
- package/dist/server-postgresql/src/services/entityService.d.ts +102 -0
- package/dist/server-postgresql/src/services/index.d.ts +4 -0
- package/dist/server-postgresql/src/services/realtimeService.d.ts +186 -0
- package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +116 -0
- package/dist/server-postgresql/src/websocket.d.ts +5 -0
- package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
- package/dist/types/src/controllers/auth.d.ts +117 -0
- package/dist/types/src/controllers/client.d.ts +58 -0
- package/dist/types/src/controllers/collection_registry.d.ts +44 -0
- package/dist/types/src/controllers/customization_controller.d.ts +54 -0
- package/dist/types/src/controllers/data.d.ts +141 -0
- package/dist/types/src/controllers/data_driver.d.ts +168 -0
- package/dist/types/src/controllers/database_admin.d.ts +11 -0
- package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
- package/dist/types/src/controllers/effective_role.d.ts +4 -0
- package/dist/types/src/controllers/index.d.ts +17 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
- package/dist/types/src/controllers/navigation.d.ts +213 -0
- package/dist/types/src/controllers/registry.d.ts +51 -0
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
- package/dist/types/src/controllers/side_entity_controller.d.ts +89 -0
- package/dist/types/src/controllers/snackbar.d.ts +24 -0
- package/dist/types/src/controllers/storage.d.ts +173 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/rebase_context.d.ts +101 -0
- package/dist/types/src/types/backend.d.ts +533 -0
- package/dist/types/src/types/builders.d.ts +14 -0
- package/dist/types/src/types/chips.d.ts +5 -0
- package/dist/types/src/types/collections.d.ts +812 -0
- package/dist/types/src/types/data_source.d.ts +64 -0
- package/dist/types/src/types/entities.d.ts +145 -0
- package/dist/types/src/types/entity_actions.d.ts +98 -0
- package/dist/types/src/types/entity_callbacks.d.ts +173 -0
- package/dist/types/src/types/entity_link_builder.d.ts +7 -0
- package/dist/types/src/types/entity_overrides.d.ts +9 -0
- package/dist/types/src/types/entity_views.d.ts +61 -0
- package/dist/types/src/types/export_import.d.ts +21 -0
- package/dist/types/src/types/index.d.ts +22 -0
- package/dist/types/src/types/locales.d.ts +4 -0
- package/dist/types/src/types/modify_collections.d.ts +5 -0
- package/dist/types/src/types/plugins.d.ts +225 -0
- package/dist/types/src/types/properties.d.ts +1091 -0
- package/dist/types/src/types/property_config.d.ts +70 -0
- package/dist/types/src/types/relations.d.ts +336 -0
- package/dist/types/src/types/slots.d.ts +228 -0
- package/dist/types/src/types/translations.d.ts +826 -0
- package/dist/types/src/types/user_management_delegate.d.ts +120 -0
- package/dist/types/src/types/websockets.d.ts +78 -0
- package/dist/types/src/users/index.d.ts +2 -0
- package/dist/types/src/users/roles.d.ts +22 -0
- package/dist/types/src/users/user.d.ts +46 -0
- package/jest-all.log +3128 -0
- package/jest.log +49 -0
- package/package.json +93 -0
- package/src/PostgresBackendDriver.ts +1024 -0
- package/src/PostgresBootstrapper.ts +232 -0
- package/src/auth/ensure-tables.ts +309 -0
- package/src/auth/services.ts +740 -0
- package/src/cli.ts +347 -0
- package/src/collections/PostgresCollectionRegistry.ts +96 -0
- package/src/connection.ts +62 -0
- package/src/data-transformer.ts +569 -0
- package/src/databasePoolManager.ts +84 -0
- package/src/history/HistoryService.ts +257 -0
- package/src/history/ensure-history-table.ts +45 -0
- package/src/index.ts +13 -0
- package/src/interfaces.ts +60 -0
- package/src/schema/auth-schema.ts +146 -0
- package/src/schema/generate-drizzle-schema-logic.ts +618 -0
- package/src/schema/generate-drizzle-schema.ts +151 -0
- package/src/services/BranchService.ts +237 -0
- package/src/services/EntityFetchService.ts +1447 -0
- package/src/services/EntityPersistService.ts +351 -0
- package/src/services/RelationService.ts +1012 -0
- package/src/services/entity-helpers.ts +121 -0
- package/src/services/entityService.ts +209 -0
- package/src/services/index.ts +13 -0
- package/src/services/realtimeService.ts +1005 -0
- package/src/utils/drizzle-conditions.ts +999 -0
- package/src/websocket.ts +487 -0
- package/test/auth-services.test.ts +569 -0
- package/test/branchService.test.ts +357 -0
- package/test/drizzle-conditions.test.ts +895 -0
- package/test/entityService.errors.test.ts +352 -0
- package/test/entityService.relations.test.ts +912 -0
- package/test/entityService.subcollection-search.test.ts +516 -0
- package/test/entityService.test.ts +977 -0
- package/test/generate-drizzle-schema.test.ts +795 -0
- package/test/historyService.test.ts +126 -0
- package/test/postgresDataDriver.test.ts +556 -0
- package/test/realtimeService.test.ts +276 -0
- package/test/relations.test.ts +662 -0
- package/test_drizzle_mock.js +3 -0
- package/test_find_changed.mjs +30 -0
- package/test_output.txt +3145 -0
- package/tsconfig.json +49 -0
- package/tsconfig.prod.json +20 -0
- package/vite.config.ts +82 -0
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
2
|
+
import { UserService, RoleService, RefreshTokenService, PasswordResetTokenService, Role } from "../src/auth/services";
|
|
3
|
+
import { users, refreshTokens, passwordResetTokens, User } from "../src/schema/auth-schema";
|
|
4
|
+
|
|
5
|
+
// Mock the drizzle-orm functions
|
|
6
|
+
jest.mock("drizzle-orm", () => ({
|
|
7
|
+
eq: jest.fn((field, value) => ({ field, value, type: "eq" })),
|
|
8
|
+
sql: jest.fn((strings: TemplateStringsArray, ...values: any[]) => ({
|
|
9
|
+
strings,
|
|
10
|
+
values,
|
|
11
|
+
type: "sql"
|
|
12
|
+
})),
|
|
13
|
+
relations: jest.fn(() => ({}))
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
describe("Auth Services", () => {
|
|
17
|
+
let db: jest.Mocked<NodePgDatabase<any>>;
|
|
18
|
+
let mockInsertValues: jest.Mock;
|
|
19
|
+
let mockInsertReturning: jest.Mock;
|
|
20
|
+
let mockSelectFrom: jest.Mock;
|
|
21
|
+
let mockSelectWhere: jest.Mock;
|
|
22
|
+
let mockUpdateSet: jest.Mock;
|
|
23
|
+
let mockUpdateWhere: jest.Mock;
|
|
24
|
+
let mockUpdateReturning: jest.Mock;
|
|
25
|
+
let mockDeleteWhere: jest.Mock;
|
|
26
|
+
let mockExecute: jest.Mock;
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
// Create chainable mocks
|
|
30
|
+
mockInsertReturning = jest.fn().mockResolvedValue([]);
|
|
31
|
+
mockInsertValues = jest.fn().mockReturnValue({
|
|
32
|
+
returning: mockInsertReturning,
|
|
33
|
+
onConflictDoUpdate: jest.fn().mockReturnValue({ returning: mockInsertReturning })
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
mockSelectWhere = jest.fn().mockResolvedValue([]);
|
|
37
|
+
mockSelectFrom = jest.fn().mockReturnValue({
|
|
38
|
+
where: mockSelectWhere
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
mockUpdateReturning = jest.fn().mockResolvedValue([]);
|
|
42
|
+
mockUpdateWhere = jest.fn().mockReturnValue({ returning: mockUpdateReturning });
|
|
43
|
+
mockUpdateSet = jest.fn().mockReturnValue({ where: mockUpdateWhere });
|
|
44
|
+
|
|
45
|
+
mockDeleteWhere = jest.fn().mockResolvedValue(undefined);
|
|
46
|
+
|
|
47
|
+
mockExecute = jest.fn().mockResolvedValue({ rows: [] });
|
|
48
|
+
|
|
49
|
+
db = {
|
|
50
|
+
insert: jest.fn().mockReturnValue({ values: mockInsertValues }),
|
|
51
|
+
select: jest.fn().mockReturnValue({ from: mockSelectFrom }),
|
|
52
|
+
update: jest.fn().mockReturnValue({ set: mockUpdateSet }),
|
|
53
|
+
delete: jest.fn().mockReturnValue({ where: mockDeleteWhere }),
|
|
54
|
+
execute: mockExecute
|
|
55
|
+
} as any;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("UserService", () => {
|
|
59
|
+
let userService: UserService;
|
|
60
|
+
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
userService = new UserService(db);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("createUser", () => {
|
|
66
|
+
it("should create a user and return it", async () => {
|
|
67
|
+
const newUser = {
|
|
68
|
+
email: "test@example.com",
|
|
69
|
+
displayName: "Test User",
|
|
70
|
+
provider: "email"
|
|
71
|
+
};
|
|
72
|
+
const createdUser = { id: "user-123", ...newUser, createdAt: new Date(), updatedAt: new Date() };
|
|
73
|
+
mockInsertReturning.mockResolvedValueOnce([createdUser]);
|
|
74
|
+
|
|
75
|
+
const result = await userService.createUser(newUser);
|
|
76
|
+
|
|
77
|
+
expect(db.insert).toHaveBeenCalledWith(users);
|
|
78
|
+
expect(mockInsertValues).toHaveBeenCalledWith(newUser);
|
|
79
|
+
expect(result).toEqual(createdUser);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("getUserById", () => {
|
|
84
|
+
it("should return user when found", async () => {
|
|
85
|
+
const mockUser = { id: "user-123", email: "test@example.com" };
|
|
86
|
+
mockSelectWhere.mockResolvedValueOnce([mockUser]);
|
|
87
|
+
|
|
88
|
+
const result = await userService.getUserById("user-123");
|
|
89
|
+
|
|
90
|
+
expect(db.select).toHaveBeenCalled();
|
|
91
|
+
expect(result).toEqual(mockUser);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should return null when user not found", async () => {
|
|
95
|
+
mockSelectWhere.mockResolvedValueOnce([]);
|
|
96
|
+
|
|
97
|
+
const result = await userService.getUserById("nonexistent");
|
|
98
|
+
|
|
99
|
+
expect(result).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("getUserByEmail", () => {
|
|
104
|
+
it("should return user when found by email", async () => {
|
|
105
|
+
const mockUser = { id: "user-123", email: "test@example.com" };
|
|
106
|
+
mockSelectWhere.mockResolvedValueOnce([mockUser]);
|
|
107
|
+
|
|
108
|
+
const result = await userService.getUserByEmail("test@example.com");
|
|
109
|
+
|
|
110
|
+
expect(result).toEqual(mockUser);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should lowercase email for lookup", async () => {
|
|
114
|
+
mockSelectWhere.mockResolvedValueOnce([]);
|
|
115
|
+
|
|
116
|
+
await userService.getUserByEmail("TEST@EXAMPLE.COM");
|
|
117
|
+
|
|
118
|
+
// The eq function will be called with lowercase email
|
|
119
|
+
expect(mockSelectWhere).toHaveBeenCalled();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("getUserByGoogleId", () => {
|
|
124
|
+
it("should return user when found by Google ID", async () => {
|
|
125
|
+
const mockUser = { id: "user-123", googleId: "google-abc" };
|
|
126
|
+
mockSelectWhere.mockResolvedValueOnce([mockUser]);
|
|
127
|
+
|
|
128
|
+
const result = await userService.getUserByGoogleId("google-abc");
|
|
129
|
+
|
|
130
|
+
expect(result).toEqual(mockUser);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("updateUser", () => {
|
|
135
|
+
it("should update user and return updated record", async () => {
|
|
136
|
+
const updatedUser = { id: "user-123", email: "test@example.com", displayName: "Updated Name" };
|
|
137
|
+
mockUpdateReturning.mockResolvedValueOnce([updatedUser]);
|
|
138
|
+
|
|
139
|
+
const result = await userService.updateUser("user-123", { displayName: "Updated Name" });
|
|
140
|
+
|
|
141
|
+
expect(db.update).toHaveBeenCalledWith(users);
|
|
142
|
+
expect(mockUpdateSet).toHaveBeenCalledWith(expect.objectContaining({
|
|
143
|
+
displayName: "Updated Name",
|
|
144
|
+
updatedAt: expect.any(Date)
|
|
145
|
+
}));
|
|
146
|
+
expect(result).toEqual(updatedUser);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should return null when user not found", async () => {
|
|
150
|
+
mockUpdateReturning.mockResolvedValueOnce([]);
|
|
151
|
+
|
|
152
|
+
const result = await userService.updateUser("nonexistent", { displayName: "Test" });
|
|
153
|
+
|
|
154
|
+
expect(result).toBeNull();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("deleteUser", () => {
|
|
159
|
+
it("should delete user by ID", async () => {
|
|
160
|
+
await userService.deleteUser("user-123");
|
|
161
|
+
|
|
162
|
+
expect(db.delete).toHaveBeenCalledWith(users);
|
|
163
|
+
expect(mockDeleteWhere).toHaveBeenCalled();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("listUsers", () => {
|
|
168
|
+
it("should return all users", async () => {
|
|
169
|
+
const mockUsers = [
|
|
170
|
+
{ id: "user-1", email: "user1@example.com" },
|
|
171
|
+
{ id: "user-2", email: "user2@example.com" }
|
|
172
|
+
];
|
|
173
|
+
mockSelectFrom.mockReturnValueOnce(Promise.resolve(mockUsers));
|
|
174
|
+
|
|
175
|
+
const result = await userService.listUsers();
|
|
176
|
+
|
|
177
|
+
expect(db.select).toHaveBeenCalled();
|
|
178
|
+
expect(result).toEqual(mockUsers);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe("updatePassword", () => {
|
|
183
|
+
it("should update password hash", async () => {
|
|
184
|
+
mockUpdateWhere.mockResolvedValueOnce(undefined);
|
|
185
|
+
|
|
186
|
+
await userService.updatePassword("user-123", "new-hash");
|
|
187
|
+
|
|
188
|
+
expect(db.update).toHaveBeenCalledWith(users);
|
|
189
|
+
expect(mockUpdateSet).toHaveBeenCalledWith(expect.objectContaining({
|
|
190
|
+
passwordHash: "new-hash",
|
|
191
|
+
updatedAt: expect.any(Date)
|
|
192
|
+
}));
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("setEmailVerified", () => {
|
|
197
|
+
it("should set email verified and clear token", async () => {
|
|
198
|
+
mockUpdateWhere.mockResolvedValueOnce(undefined);
|
|
199
|
+
|
|
200
|
+
await userService.setEmailVerified("user-123", true);
|
|
201
|
+
|
|
202
|
+
expect(mockUpdateSet).toHaveBeenCalledWith(expect.objectContaining({
|
|
203
|
+
emailVerified: true,
|
|
204
|
+
emailVerificationToken: null,
|
|
205
|
+
updatedAt: expect.any(Date)
|
|
206
|
+
}));
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe("setVerificationToken", () => {
|
|
211
|
+
it("should set verification token", async () => {
|
|
212
|
+
mockUpdateWhere.mockResolvedValueOnce(undefined);
|
|
213
|
+
|
|
214
|
+
await userService.setVerificationToken("user-123", "token-abc");
|
|
215
|
+
|
|
216
|
+
expect(mockUpdateSet).toHaveBeenCalledWith(expect.objectContaining({
|
|
217
|
+
emailVerificationToken: "token-abc",
|
|
218
|
+
emailVerificationSentAt: expect.any(Date),
|
|
219
|
+
updatedAt: expect.any(Date)
|
|
220
|
+
}));
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("should clear verification token when null", async () => {
|
|
224
|
+
mockUpdateWhere.mockResolvedValueOnce(undefined);
|
|
225
|
+
|
|
226
|
+
await userService.setVerificationToken("user-123", null);
|
|
227
|
+
|
|
228
|
+
expect(mockUpdateSet).toHaveBeenCalledWith(expect.objectContaining({
|
|
229
|
+
emailVerificationToken: null,
|
|
230
|
+
emailVerificationSentAt: null,
|
|
231
|
+
updatedAt: expect.any(Date)
|
|
232
|
+
}));
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe("getUserByVerificationToken", () => {
|
|
237
|
+
it("should find user by verification token", async () => {
|
|
238
|
+
const mockUser = { id: "user-123", email: "test@example.com" };
|
|
239
|
+
mockSelectWhere.mockResolvedValueOnce([mockUser]);
|
|
240
|
+
|
|
241
|
+
const result = await userService.getUserByVerificationToken("token-abc");
|
|
242
|
+
|
|
243
|
+
expect(result).toEqual(mockUser);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe("getUserRoles", () => {
|
|
248
|
+
it("should return roles for user", async () => {
|
|
249
|
+
mockExecute.mockResolvedValueOnce({
|
|
250
|
+
rows: [
|
|
251
|
+
{ id: "admin", name: "Admin", is_admin: true, default_permissions: null, collection_permissions: null, config: null },
|
|
252
|
+
{ id: "editor", name: "Editor", is_admin: false, default_permissions: { edit: true }, collection_permissions: null, config: null }
|
|
253
|
+
]
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const roles = await userService.getUserRoles("user-123");
|
|
257
|
+
|
|
258
|
+
expect(roles).toHaveLength(2);
|
|
259
|
+
expect(roles[0]).toEqual({
|
|
260
|
+
id: "admin",
|
|
261
|
+
name: "Admin",
|
|
262
|
+
isAdmin: true,
|
|
263
|
+
defaultPermissions: null,
|
|
264
|
+
collectionPermissions: null,
|
|
265
|
+
config: null
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe("getUserRoleIds", () => {
|
|
271
|
+
it("should return role IDs for user", async () => {
|
|
272
|
+
mockExecute.mockResolvedValueOnce({
|
|
273
|
+
rows: [
|
|
274
|
+
{ id: "admin", name: "Admin", is_admin: true, default_permissions: null, collection_permissions: null, config: null }
|
|
275
|
+
]
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const roleIds = await userService.getUserRoleIds("user-123");
|
|
279
|
+
|
|
280
|
+
expect(roleIds).toEqual(["admin"]);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe("setUserRoles", () => {
|
|
285
|
+
it("should delete existing and insert new roles", async () => {
|
|
286
|
+
await userService.setUserRoles("user-123", ["admin", "editor"]);
|
|
287
|
+
|
|
288
|
+
// First call deletes existing roles
|
|
289
|
+
expect(mockExecute).toHaveBeenCalled();
|
|
290
|
+
// Subsequent calls insert new roles
|
|
291
|
+
expect(mockExecute.mock.calls.length).toBeGreaterThanOrEqual(1);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
describe("assignDefaultRole", () => {
|
|
296
|
+
it("should assign default role to user", async () => {
|
|
297
|
+
await userService.assignDefaultRole("user-123", "editor");
|
|
298
|
+
|
|
299
|
+
expect(mockExecute).toHaveBeenCalled();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("should use editor as default role", async () => {
|
|
303
|
+
await userService.assignDefaultRole("user-123");
|
|
304
|
+
|
|
305
|
+
expect(mockExecute).toHaveBeenCalled();
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe("getUserWithRoles", () => {
|
|
310
|
+
it("should return user with roles", async () => {
|
|
311
|
+
const mockUser = { id: "user-123", email: "test@example.com" };
|
|
312
|
+
mockSelectWhere.mockResolvedValueOnce([mockUser]);
|
|
313
|
+
mockExecute.mockResolvedValueOnce({
|
|
314
|
+
rows: [{ id: "admin", name: "Admin", is_admin: true, default_permissions: null, collection_permissions: null, config: null }]
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const result = await userService.getUserWithRoles("user-123");
|
|
318
|
+
|
|
319
|
+
expect(result).toEqual({
|
|
320
|
+
user: mockUser,
|
|
321
|
+
roles: [{ id: "admin", name: "Admin", isAdmin: true, defaultPermissions: null, collectionPermissions: null, config: null }]
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("should return null when user not found", async () => {
|
|
326
|
+
mockSelectWhere.mockResolvedValueOnce([]);
|
|
327
|
+
|
|
328
|
+
const result = await userService.getUserWithRoles("nonexistent");
|
|
329
|
+
|
|
330
|
+
expect(result).toBeNull();
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe("RoleService", () => {
|
|
336
|
+
let roleService: RoleService;
|
|
337
|
+
|
|
338
|
+
beforeEach(() => {
|
|
339
|
+
roleService = new RoleService(db);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
describe("getRoleById", () => {
|
|
343
|
+
it("should return role when found", async () => {
|
|
344
|
+
mockExecute.mockResolvedValueOnce({
|
|
345
|
+
rows: [{ id: "admin", name: "Admin", is_admin: true, default_permissions: null, collection_permissions: null, config: null }]
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const result = await roleService.getRoleById("admin");
|
|
349
|
+
|
|
350
|
+
expect(result).toEqual({
|
|
351
|
+
id: "admin",
|
|
352
|
+
name: "Admin",
|
|
353
|
+
isAdmin: true,
|
|
354
|
+
defaultPermissions: null,
|
|
355
|
+
collectionPermissions: null,
|
|
356
|
+
config: null
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("should return null when role not found", async () => {
|
|
361
|
+
mockExecute.mockResolvedValueOnce({ rows: [] });
|
|
362
|
+
|
|
363
|
+
const result = await roleService.getRoleById("nonexistent");
|
|
364
|
+
|
|
365
|
+
expect(result).toBeNull();
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe("listRoles", () => {
|
|
370
|
+
it("should return all roles", async () => {
|
|
371
|
+
mockExecute.mockResolvedValueOnce({
|
|
372
|
+
rows: [
|
|
373
|
+
{ id: "admin", name: "Admin", is_admin: true, default_permissions: null, collection_permissions: null, config: null },
|
|
374
|
+
{ id: "editor", name: "Editor", is_admin: false, default_permissions: null, collection_permissions: null, config: null }
|
|
375
|
+
]
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const roles = await roleService.listRoles();
|
|
379
|
+
|
|
380
|
+
expect(roles).toHaveLength(2);
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
describe("createRole", () => {
|
|
385
|
+
it("should create a role", async () => {
|
|
386
|
+
mockExecute.mockResolvedValueOnce({
|
|
387
|
+
rows: [{ id: "custom", name: "Custom Role", is_admin: false, default_permissions: null, collection_permissions: null, config: null }]
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const role = await roleService.createRole({
|
|
391
|
+
id: "custom",
|
|
392
|
+
name: "Custom Role",
|
|
393
|
+
defaultPermissions: null,
|
|
394
|
+
config: null
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
expect(role.id).toBe("custom");
|
|
398
|
+
expect(role.name).toBe("Custom Role");
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
describe("updateRole", () => {
|
|
403
|
+
it("should update a role", async () => {
|
|
404
|
+
mockExecute
|
|
405
|
+
.mockResolvedValueOnce({ rows: [{ id: "admin", name: "Admin", is_admin: true, default_permissions: null, collection_permissions: null, config: null }] })
|
|
406
|
+
.mockResolvedValueOnce({ rows: [] })
|
|
407
|
+
.mockResolvedValueOnce({ rows: [{ id: "admin", name: "Super Admin", is_admin: true, default_permissions: null, collection_permissions: null, config: null }] });
|
|
408
|
+
|
|
409
|
+
const result = await roleService.updateRole("admin", { name: "Super Admin" });
|
|
410
|
+
|
|
411
|
+
expect(result?.name).toBe("Super Admin");
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it("should return null when role not found", async () => {
|
|
415
|
+
mockExecute.mockResolvedValueOnce({ rows: [] });
|
|
416
|
+
|
|
417
|
+
const result = await roleService.updateRole("nonexistent", { name: "Test" });
|
|
418
|
+
|
|
419
|
+
expect(result).toBeNull();
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
describe("deleteRole", () => {
|
|
424
|
+
it("should delete a role", async () => {
|
|
425
|
+
await roleService.deleteRole("custom");
|
|
426
|
+
|
|
427
|
+
expect(mockExecute).toHaveBeenCalled();
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
describe("RefreshTokenService", () => {
|
|
433
|
+
let refreshTokenService: RefreshTokenService;
|
|
434
|
+
|
|
435
|
+
beforeEach(() => {
|
|
436
|
+
refreshTokenService = new RefreshTokenService(db);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
describe("createToken", () => {
|
|
440
|
+
it("should create a refresh token", async () => {
|
|
441
|
+
const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
|
|
442
|
+
|
|
443
|
+
await refreshTokenService.createToken("user-123", "token-hash", expiresAt);
|
|
444
|
+
|
|
445
|
+
expect(db.insert).toHaveBeenCalledWith(refreshTokens);
|
|
446
|
+
expect(mockInsertValues).toHaveBeenCalledWith({
|
|
447
|
+
userId: "user-123",
|
|
448
|
+
tokenHash: "token-hash",
|
|
449
|
+
expiresAt,
|
|
450
|
+
ipAddress: "",
|
|
451
|
+
userAgent: ""
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
describe("findByHash", () => {
|
|
457
|
+
it("should find token by hash", async () => {
|
|
458
|
+
const expiresAt = new Date();
|
|
459
|
+
mockSelectWhere.mockResolvedValueOnce([{ userId: "user-123", expiresAt }]);
|
|
460
|
+
|
|
461
|
+
const result = await refreshTokenService.findByHash("token-hash");
|
|
462
|
+
|
|
463
|
+
expect(result).toEqual({ userId: "user-123", expiresAt });
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it("should return null when token not found", async () => {
|
|
467
|
+
mockSelectWhere.mockResolvedValueOnce([]);
|
|
468
|
+
|
|
469
|
+
const result = await refreshTokenService.findByHash("nonexistent");
|
|
470
|
+
|
|
471
|
+
expect(result).toBeNull();
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
describe("deleteByHash", () => {
|
|
476
|
+
it("should delete token by hash", async () => {
|
|
477
|
+
await refreshTokenService.deleteByHash("token-hash");
|
|
478
|
+
|
|
479
|
+
expect(db.delete).toHaveBeenCalledWith(refreshTokens);
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
describe("deleteAllForUser", () => {
|
|
484
|
+
it("should delete all tokens for user", async () => {
|
|
485
|
+
await refreshTokenService.deleteAllForUser("user-123");
|
|
486
|
+
|
|
487
|
+
expect(db.delete).toHaveBeenCalledWith(refreshTokens);
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
describe("PasswordResetTokenService", () => {
|
|
493
|
+
let passwordResetTokenService: PasswordResetTokenService;
|
|
494
|
+
|
|
495
|
+
beforeEach(() => {
|
|
496
|
+
passwordResetTokenService = new PasswordResetTokenService(db);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
describe("createToken", () => {
|
|
500
|
+
it("should delete existing tokens and create new one", async () => {
|
|
501
|
+
const expiresAt = new Date(Date.now() + 60 * 60 * 1000);
|
|
502
|
+
|
|
503
|
+
await passwordResetTokenService.createToken("user-123", "token-hash", expiresAt);
|
|
504
|
+
|
|
505
|
+
// First deletes existing unused tokens
|
|
506
|
+
expect(mockExecute).toHaveBeenCalled();
|
|
507
|
+
// Then inserts new token
|
|
508
|
+
expect(db.insert).toHaveBeenCalledWith(passwordResetTokens);
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
describe("findValidByHash", () => {
|
|
513
|
+
it("should find valid token", async () => {
|
|
514
|
+
const expiresAt = new Date(Date.now() + 60 * 60 * 1000);
|
|
515
|
+
mockSelectWhere.mockResolvedValueOnce([{ userId: "user-123", expiresAt }]);
|
|
516
|
+
mockExecute.mockResolvedValueOnce({
|
|
517
|
+
rows: [{ user_id: "user-123", expires_at: expiresAt }]
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
const result = await passwordResetTokenService.findValidByHash("token-hash");
|
|
521
|
+
|
|
522
|
+
expect(result).toEqual({ userId: "user-123", expiresAt });
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("should return null when token not found", async () => {
|
|
526
|
+
mockSelectWhere.mockResolvedValueOnce([]);
|
|
527
|
+
|
|
528
|
+
const result = await passwordResetTokenService.findValidByHash("nonexistent");
|
|
529
|
+
|
|
530
|
+
expect(result).toBeNull();
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it("should return null when token is expired or used", async () => {
|
|
534
|
+
const expiresAt = new Date(Date.now() + 60 * 60 * 1000);
|
|
535
|
+
mockSelectWhere.mockResolvedValueOnce([{ userId: "user-123", expiresAt }]);
|
|
536
|
+
mockExecute.mockResolvedValueOnce({ rows: [] }); // No valid token found
|
|
537
|
+
|
|
538
|
+
const result = await passwordResetTokenService.findValidByHash("token-hash");
|
|
539
|
+
|
|
540
|
+
expect(result).toBeNull();
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
describe("markAsUsed", () => {
|
|
545
|
+
it("should mark token as used", async () => {
|
|
546
|
+
await passwordResetTokenService.markAsUsed("token-hash");
|
|
547
|
+
|
|
548
|
+
expect(db.update).toHaveBeenCalledWith(passwordResetTokens);
|
|
549
|
+
expect(mockUpdateSet).toHaveBeenCalledWith({ usedAt: expect.any(Date) });
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
describe("deleteAllForUser", () => {
|
|
554
|
+
it("should delete all tokens for user", async () => {
|
|
555
|
+
await passwordResetTokenService.deleteAllForUser("user-123");
|
|
556
|
+
|
|
557
|
+
expect(db.delete).toHaveBeenCalledWith(passwordResetTokens);
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
describe("deleteExpired", () => {
|
|
562
|
+
it("should delete expired tokens", async () => {
|
|
563
|
+
await passwordResetTokenService.deleteExpired();
|
|
564
|
+
|
|
565
|
+
expect(mockExecute).toHaveBeenCalled();
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
});
|