@emkodev/emkore 1.0.3
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/CHANGELOG.md +269 -0
- package/DEVELOPER_GUIDE.md +227 -0
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/bun.lock +22 -0
- package/example/README.md +200 -0
- package/example/create-user.interactor.ts +88 -0
- package/example/dto/user.dto.ts +34 -0
- package/example/entity/user.entity.ts +54 -0
- package/example/index.ts +18 -0
- package/example/interface/create-user.usecase.ts +93 -0
- package/example/interface/user.repository.ts +23 -0
- package/mod.ts +1 -0
- package/package.json +32 -0
- package/src/common/abstract.actor.ts +59 -0
- package/src/common/abstract.entity.ts +59 -0
- package/src/common/abstract.interceptor.ts +17 -0
- package/src/common/abstract.repository.ts +162 -0
- package/src/common/abstract.usecase.ts +113 -0
- package/src/common/config/config-registry.ts +190 -0
- package/src/common/config/config-section.ts +106 -0
- package/src/common/exception/authorization-exception.ts +28 -0
- package/src/common/exception/repository-exception.ts +46 -0
- package/src/common/interceptor/audit-log.interceptor.ts +181 -0
- package/src/common/interceptor/authorization.interceptor.ts +252 -0
- package/src/common/interceptor/performance.interceptor.ts +101 -0
- package/src/common/llm/api-definition.type.ts +185 -0
- package/src/common/pattern/unit-of-work.ts +78 -0
- package/src/common/platform/env.ts +38 -0
- package/src/common/registry/usecase-registry.ts +80 -0
- package/src/common/type/interceptor-context.type.ts +25 -0
- package/src/common/type/json-schema.type.ts +80 -0
- package/src/common/type/json.type.ts +5 -0
- package/src/common/type/lowercase.type.ts +48 -0
- package/src/common/type/metadata.type.ts +5 -0
- package/src/common/type/money.class.ts +384 -0
- package/src/common/type/permission.type.ts +43 -0
- package/src/common/validation/validation-result.ts +52 -0
- package/src/common/validation/validators.ts +441 -0
- package/src/index.ts +95 -0
- package/test/unit/abstract-actor.test.ts +608 -0
- package/test/unit/actor.test.ts +89 -0
- package/test/unit/api-definition.test.ts +628 -0
- package/test/unit/authorization.test.ts +101 -0
- package/test/unit/entity.test.ts +95 -0
- package/test/unit/money.test.ts +480 -0
- package/test/unit/validation.test.ts +138 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
import { test, expect, describe } from "bun:test";
|
|
2
|
+
import { Actor } from "../../src/common/abstract.actor.ts";
|
|
3
|
+
import type { Permission } from "../../src/common/type/permission.type.ts";
|
|
4
|
+
import { ResourceScope } from "../../src/common/type/permission.type.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Concrete Actor implementation for testing.
|
|
8
|
+
* The abstract Actor class requires concrete implementations of id, businessId, and token.
|
|
9
|
+
*/
|
|
10
|
+
class TestActor extends Actor {
|
|
11
|
+
constructor(
|
|
12
|
+
private _id: string,
|
|
13
|
+
private _businessId: string,
|
|
14
|
+
private _token: string,
|
|
15
|
+
permissions: Permission[] = [],
|
|
16
|
+
) {
|
|
17
|
+
super();
|
|
18
|
+
this.permissions = permissions;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get id(): string {
|
|
22
|
+
return this._id;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get businessId(): string {
|
|
26
|
+
return this._businessId;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get token(): string {
|
|
30
|
+
return this._token;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("Actor.getTeamIds() - Team extraction from permissions", () => {
|
|
35
|
+
test("should return empty array when no permissions", () => {
|
|
36
|
+
const actor = new TestActor(
|
|
37
|
+
"1001-user-1",
|
|
38
|
+
"0002-business-1",
|
|
39
|
+
"token-123",
|
|
40
|
+
[],
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const teamIds = actor.getTeamIds();
|
|
44
|
+
|
|
45
|
+
expect(teamIds).toEqual([]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("should return empty array when no team permissions", () => {
|
|
49
|
+
const actor = new TestActor(
|
|
50
|
+
"1001-user-1",
|
|
51
|
+
"0002-business-1",
|
|
52
|
+
"token-123",
|
|
53
|
+
[
|
|
54
|
+
{
|
|
55
|
+
resource: "product",
|
|
56
|
+
action: "create",
|
|
57
|
+
constraints: { scope: ResourceScope.OWNED },
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
resource: "document",
|
|
61
|
+
action: "retrieve",
|
|
62
|
+
constraints: { scope: ResourceScope.BUSINESS },
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const teamIds = actor.getTeamIds();
|
|
68
|
+
|
|
69
|
+
expect(teamIds).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("should extract single team ID from permissions", () => {
|
|
73
|
+
const actor = new TestActor(
|
|
74
|
+
"1001-user-1",
|
|
75
|
+
"0002-business-1",
|
|
76
|
+
"token-123",
|
|
77
|
+
[
|
|
78
|
+
{
|
|
79
|
+
resource: "document",
|
|
80
|
+
action: "retrieve",
|
|
81
|
+
constraints: {
|
|
82
|
+
scope: ResourceScope.TEAM,
|
|
83
|
+
teamId: "1100-legal",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const teamIds = actor.getTeamIds();
|
|
90
|
+
|
|
91
|
+
expect(teamIds).toEqual(["1100-legal"]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("should extract multiple unique team IDs", () => {
|
|
95
|
+
const actor = new TestActor(
|
|
96
|
+
"1001-user-1",
|
|
97
|
+
"0002-business-1",
|
|
98
|
+
"token-123",
|
|
99
|
+
[
|
|
100
|
+
{
|
|
101
|
+
resource: "document",
|
|
102
|
+
action: "retrieve",
|
|
103
|
+
constraints: {
|
|
104
|
+
scope: ResourceScope.TEAM,
|
|
105
|
+
teamId: "1100-legal",
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
resource: "customer",
|
|
110
|
+
action: "create",
|
|
111
|
+
constraints: {
|
|
112
|
+
scope: ResourceScope.TEAM,
|
|
113
|
+
teamId: "1100-sales",
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
resource: "product",
|
|
118
|
+
action: "update",
|
|
119
|
+
constraints: {
|
|
120
|
+
scope: ResourceScope.TEAM,
|
|
121
|
+
teamId: "1100-engineering",
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const teamIds = actor.getTeamIds();
|
|
128
|
+
|
|
129
|
+
expect(teamIds.length).toEqual(3);
|
|
130
|
+
expect(teamIds.includes("1100-legal")).toEqual(true);
|
|
131
|
+
expect(teamIds.includes("1100-sales")).toEqual(true);
|
|
132
|
+
expect(teamIds.includes("1100-engineering")).toEqual(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("should deduplicate team IDs from multiple permissions", () => {
|
|
136
|
+
const actor = new TestActor(
|
|
137
|
+
"1001-user-1",
|
|
138
|
+
"0002-business-1",
|
|
139
|
+
"token-123",
|
|
140
|
+
[
|
|
141
|
+
{
|
|
142
|
+
resource: "document",
|
|
143
|
+
action: "retrieve",
|
|
144
|
+
constraints: {
|
|
145
|
+
scope: ResourceScope.TEAM,
|
|
146
|
+
teamId: "1100-legal",
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
resource: "document",
|
|
151
|
+
action: "create",
|
|
152
|
+
constraints: {
|
|
153
|
+
scope: ResourceScope.TEAM,
|
|
154
|
+
teamId: "1100-legal",
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
resource: "document",
|
|
159
|
+
action: "update",
|
|
160
|
+
constraints: {
|
|
161
|
+
scope: ResourceScope.TEAM,
|
|
162
|
+
teamId: "1100-legal",
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
resource: "customer",
|
|
167
|
+
action: "retrieve",
|
|
168
|
+
constraints: {
|
|
169
|
+
scope: ResourceScope.TEAM,
|
|
170
|
+
teamId: "1100-sales",
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
resource: "customer",
|
|
175
|
+
action: "create",
|
|
176
|
+
constraints: {
|
|
177
|
+
scope: ResourceScope.TEAM,
|
|
178
|
+
teamId: "1100-sales",
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const teamIds = actor.getTeamIds();
|
|
185
|
+
|
|
186
|
+
// Should have exactly 2 unique team IDs despite 5 permissions
|
|
187
|
+
expect(teamIds.length).toEqual(2);
|
|
188
|
+
expect(teamIds.includes("1100-legal")).toEqual(true);
|
|
189
|
+
expect(teamIds.includes("1100-sales")).toEqual(true);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("should ignore permissions without teamId constraint", () => {
|
|
193
|
+
const actor = new TestActor(
|
|
194
|
+
"1001-user-1",
|
|
195
|
+
"0002-business-1",
|
|
196
|
+
"token-123",
|
|
197
|
+
[
|
|
198
|
+
{
|
|
199
|
+
resource: "document",
|
|
200
|
+
action: "retrieve",
|
|
201
|
+
constraints: {
|
|
202
|
+
scope: ResourceScope.TEAM,
|
|
203
|
+
teamId: "1100-legal",
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
resource: "product",
|
|
208
|
+
action: "create",
|
|
209
|
+
constraints: { scope: ResourceScope.OWNED },
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
resource: "order",
|
|
213
|
+
action: "list",
|
|
214
|
+
constraints: { scope: ResourceScope.BUSINESS },
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
resource: "invoice",
|
|
218
|
+
action: "retrieve",
|
|
219
|
+
// No constraints at all
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const teamIds = actor.getTeamIds();
|
|
225
|
+
|
|
226
|
+
expect(teamIds).toEqual(["1100-legal"]);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("should handle permissions with teamId but no scope", () => {
|
|
230
|
+
// Edge case: permission has teamId but scope is not TEAM
|
|
231
|
+
const actor = new TestActor(
|
|
232
|
+
"1001-user-1",
|
|
233
|
+
"0002-business-1",
|
|
234
|
+
"token-123",
|
|
235
|
+
[
|
|
236
|
+
{
|
|
237
|
+
resource: "document",
|
|
238
|
+
action: "retrieve",
|
|
239
|
+
constraints: {
|
|
240
|
+
scope: ResourceScope.OWNED,
|
|
241
|
+
teamId: "1100-legal", // Has teamId despite OWNED scope
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
resource: "customer",
|
|
246
|
+
action: "create",
|
|
247
|
+
constraints: {
|
|
248
|
+
teamId: "1100-sales", // Has teamId with no scope
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
const teamIds = actor.getTeamIds();
|
|
255
|
+
|
|
256
|
+
// Should extract teamIds regardless of scope value
|
|
257
|
+
expect(teamIds.length).toEqual(2);
|
|
258
|
+
expect(teamIds.includes("1100-legal")).toEqual(true);
|
|
259
|
+
expect(teamIds.includes("1100-sales")).toEqual(true);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
describe("Actor.getTeamIds() - Caching behavior", () => {
|
|
264
|
+
test("should cache result on first call", () => {
|
|
265
|
+
const actor = new TestActor(
|
|
266
|
+
"1001-user-1",
|
|
267
|
+
"0002-business-1",
|
|
268
|
+
"token-123",
|
|
269
|
+
[
|
|
270
|
+
{
|
|
271
|
+
resource: "document",
|
|
272
|
+
action: "retrieve",
|
|
273
|
+
constraints: {
|
|
274
|
+
scope: ResourceScope.TEAM,
|
|
275
|
+
teamId: "1100-legal",
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
const firstCall = actor.getTeamIds();
|
|
282
|
+
const secondCall = actor.getTeamIds();
|
|
283
|
+
|
|
284
|
+
// Should return same array reference (cached)
|
|
285
|
+
expect(firstCall).toEqual(secondCall);
|
|
286
|
+
expect(firstCall === secondCall).toEqual(true);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("should invalidate cache when permissions are set", () => {
|
|
290
|
+
const actor = new TestActor(
|
|
291
|
+
"1001-user-1",
|
|
292
|
+
"0002-business-1",
|
|
293
|
+
"token-123",
|
|
294
|
+
[
|
|
295
|
+
{
|
|
296
|
+
resource: "document",
|
|
297
|
+
action: "retrieve",
|
|
298
|
+
constraints: {
|
|
299
|
+
scope: ResourceScope.TEAM,
|
|
300
|
+
teamId: "1100-legal",
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const firstCall = actor.getTeamIds();
|
|
307
|
+
expect(firstCall).toEqual(["1100-legal"]);
|
|
308
|
+
|
|
309
|
+
// Update permissions
|
|
310
|
+
actor.permissions = [
|
|
311
|
+
{
|
|
312
|
+
resource: "customer",
|
|
313
|
+
action: "create",
|
|
314
|
+
constraints: {
|
|
315
|
+
scope: ResourceScope.TEAM,
|
|
316
|
+
teamId: "1100-sales",
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
const secondCall = actor.getTeamIds();
|
|
322
|
+
|
|
323
|
+
// Should return new team IDs after permissions change
|
|
324
|
+
expect(secondCall).toEqual(["1100-sales"]);
|
|
325
|
+
expect(firstCall).not.toEqual(secondCall);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("should invalidate cache when permission is added", () => {
|
|
329
|
+
const actor = new TestActor(
|
|
330
|
+
"1001-user-1",
|
|
331
|
+
"0002-business-1",
|
|
332
|
+
"token-123",
|
|
333
|
+
[
|
|
334
|
+
{
|
|
335
|
+
resource: "document",
|
|
336
|
+
action: "retrieve",
|
|
337
|
+
constraints: {
|
|
338
|
+
scope: ResourceScope.TEAM,
|
|
339
|
+
teamId: "1100-legal",
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
const firstCall = actor.getTeamIds();
|
|
346
|
+
expect(firstCall).toEqual(["1100-legal"]);
|
|
347
|
+
|
|
348
|
+
// Add new permission
|
|
349
|
+
actor.addPermission({
|
|
350
|
+
resource: "customer",
|
|
351
|
+
action: "create",
|
|
352
|
+
constraints: {
|
|
353
|
+
scope: ResourceScope.TEAM,
|
|
354
|
+
teamId: "1100-sales",
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
const secondCall = actor.getTeamIds();
|
|
359
|
+
|
|
360
|
+
// Should include new team
|
|
361
|
+
expect(secondCall.length).toEqual(2);
|
|
362
|
+
expect(secondCall.includes("1100-legal")).toEqual(true);
|
|
363
|
+
expect(secondCall.includes("1100-sales")).toEqual(true);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("should invalidate cache when permissions are added", () => {
|
|
367
|
+
const actor = new TestActor(
|
|
368
|
+
"1001-user-1",
|
|
369
|
+
"0002-business-1",
|
|
370
|
+
"token-123",
|
|
371
|
+
[
|
|
372
|
+
{
|
|
373
|
+
resource: "document",
|
|
374
|
+
action: "retrieve",
|
|
375
|
+
constraints: {
|
|
376
|
+
scope: ResourceScope.TEAM,
|
|
377
|
+
teamId: "1100-legal",
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
const firstCall = actor.getTeamIds();
|
|
384
|
+
expect(firstCall).toEqual(["1100-legal"]);
|
|
385
|
+
|
|
386
|
+
// Add multiple permissions
|
|
387
|
+
actor.addPermissions([
|
|
388
|
+
{
|
|
389
|
+
resource: "customer",
|
|
390
|
+
action: "create",
|
|
391
|
+
constraints: {
|
|
392
|
+
scope: ResourceScope.TEAM,
|
|
393
|
+
teamId: "1100-sales",
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
resource: "product",
|
|
398
|
+
action: "update",
|
|
399
|
+
constraints: {
|
|
400
|
+
scope: ResourceScope.TEAM,
|
|
401
|
+
teamId: "1100-engineering",
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
]);
|
|
405
|
+
|
|
406
|
+
const secondCall = actor.getTeamIds();
|
|
407
|
+
|
|
408
|
+
// Should include all teams
|
|
409
|
+
expect(secondCall.length).toEqual(3);
|
|
410
|
+
expect(secondCall.includes("1100-legal")).toEqual(true);
|
|
411
|
+
expect(secondCall.includes("1100-sales")).toEqual(true);
|
|
412
|
+
expect(secondCall.includes("1100-engineering")).toEqual(true);
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
describe("Actor.getTeamIds() - Real-world scenarios", () => {
|
|
417
|
+
test("Legal team member with document permissions", () => {
|
|
418
|
+
const actor = new TestActor(
|
|
419
|
+
"1001-alice",
|
|
420
|
+
"0002-techcorp",
|
|
421
|
+
"token-alice",
|
|
422
|
+
[
|
|
423
|
+
{
|
|
424
|
+
resource: "document",
|
|
425
|
+
action: "retrieve",
|
|
426
|
+
constraints: {
|
|
427
|
+
scope: ResourceScope.TEAM,
|
|
428
|
+
teamId: "1100-legal",
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
resource: "document",
|
|
433
|
+
action: "create",
|
|
434
|
+
constraints: {
|
|
435
|
+
scope: ResourceScope.TEAM,
|
|
436
|
+
teamId: "1100-legal",
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
resource: "document",
|
|
441
|
+
action: "update",
|
|
442
|
+
constraints: {
|
|
443
|
+
scope: ResourceScope.TEAM,
|
|
444
|
+
teamId: "1100-legal",
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
// Also has personal permissions
|
|
448
|
+
{
|
|
449
|
+
resource: "product",
|
|
450
|
+
action: "retrieve",
|
|
451
|
+
constraints: {
|
|
452
|
+
scope: ResourceScope.OWNED,
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
const teamIds = actor.getTeamIds();
|
|
459
|
+
|
|
460
|
+
expect(teamIds).toEqual(["1100-legal"]);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
test("Multi-team member (Legal + Sales)", () => {
|
|
464
|
+
const actor = new TestActor(
|
|
465
|
+
"1001-bob",
|
|
466
|
+
"0002-techcorp",
|
|
467
|
+
"token-bob",
|
|
468
|
+
[
|
|
469
|
+
// Legal team permissions
|
|
470
|
+
{
|
|
471
|
+
resource: "document",
|
|
472
|
+
action: "retrieve",
|
|
473
|
+
constraints: {
|
|
474
|
+
scope: ResourceScope.TEAM,
|
|
475
|
+
teamId: "1100-legal",
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
resource: "document",
|
|
480
|
+
action: "create",
|
|
481
|
+
constraints: {
|
|
482
|
+
scope: ResourceScope.TEAM,
|
|
483
|
+
teamId: "1100-legal",
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
// Sales team permissions
|
|
487
|
+
{
|
|
488
|
+
resource: "customer",
|
|
489
|
+
action: "retrieve",
|
|
490
|
+
constraints: {
|
|
491
|
+
scope: ResourceScope.TEAM,
|
|
492
|
+
teamId: "1100-sales",
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
resource: "customer",
|
|
497
|
+
action: "create",
|
|
498
|
+
constraints: {
|
|
499
|
+
scope: ResourceScope.TEAM,
|
|
500
|
+
teamId: "1100-sales",
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
resource: "quote",
|
|
505
|
+
action: "create",
|
|
506
|
+
constraints: {
|
|
507
|
+
scope: ResourceScope.TEAM,
|
|
508
|
+
teamId: "1100-sales",
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
],
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
const teamIds = actor.getTeamIds();
|
|
515
|
+
|
|
516
|
+
expect(teamIds.length).toEqual(2);
|
|
517
|
+
expect(teamIds.includes("1100-legal")).toEqual(true);
|
|
518
|
+
expect(teamIds.includes("1100-sales")).toEqual(true);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
test("Business owner with BUSINESS scope (no teams)", () => {
|
|
522
|
+
const actor = new TestActor(
|
|
523
|
+
"1001-owner",
|
|
524
|
+
"0002-techcorp",
|
|
525
|
+
"token-owner",
|
|
526
|
+
[
|
|
527
|
+
{
|
|
528
|
+
resource: "product",
|
|
529
|
+
action: "create",
|
|
530
|
+
constraints: {
|
|
531
|
+
scope: ResourceScope.BUSINESS,
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
resource: "product",
|
|
536
|
+
action: "retrieve",
|
|
537
|
+
constraints: {
|
|
538
|
+
scope: ResourceScope.BUSINESS,
|
|
539
|
+
},
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
resource: "user",
|
|
543
|
+
action: "create",
|
|
544
|
+
constraints: {
|
|
545
|
+
scope: ResourceScope.BUSINESS,
|
|
546
|
+
},
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
resource: "team",
|
|
550
|
+
action: "create",
|
|
551
|
+
constraints: {
|
|
552
|
+
scope: ResourceScope.BUSINESS,
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
],
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
const teamIds = actor.getTeamIds();
|
|
559
|
+
|
|
560
|
+
expect(teamIds).toEqual([]);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
test("Employee with mixed scope permissions", () => {
|
|
564
|
+
const actor = new TestActor(
|
|
565
|
+
"1001-carol",
|
|
566
|
+
"0002-techcorp",
|
|
567
|
+
"token-carol",
|
|
568
|
+
[
|
|
569
|
+
// Team permissions
|
|
570
|
+
{
|
|
571
|
+
resource: "document",
|
|
572
|
+
action: "retrieve",
|
|
573
|
+
constraints: {
|
|
574
|
+
scope: ResourceScope.TEAM,
|
|
575
|
+
teamId: "1100-engineering",
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
// Personal permissions
|
|
579
|
+
{
|
|
580
|
+
resource: "task",
|
|
581
|
+
action: "create",
|
|
582
|
+
constraints: {
|
|
583
|
+
scope: ResourceScope.OWNED,
|
|
584
|
+
},
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
resource: "task",
|
|
588
|
+
action: "retrieve",
|
|
589
|
+
constraints: {
|
|
590
|
+
scope: ResourceScope.OWNED,
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
// Business-wide read permissions
|
|
594
|
+
{
|
|
595
|
+
resource: "product",
|
|
596
|
+
action: "retrieve",
|
|
597
|
+
constraints: {
|
|
598
|
+
scope: ResourceScope.ALL,
|
|
599
|
+
},
|
|
600
|
+
},
|
|
601
|
+
],
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
const teamIds = actor.getTeamIds();
|
|
605
|
+
|
|
606
|
+
expect(teamIds).toEqual(["1100-engineering"]);
|
|
607
|
+
});
|
|
608
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { test, expect } from "bun:test";
|
|
2
|
+
import { Actor, type Permission } from "../../mod.ts";
|
|
3
|
+
|
|
4
|
+
class TestActor extends Actor {
|
|
5
|
+
constructor(
|
|
6
|
+
public readonly id: string,
|
|
7
|
+
public readonly businessId: string,
|
|
8
|
+
public readonly token: string,
|
|
9
|
+
) {
|
|
10
|
+
super();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
test("Actor - basic properties", () => {
|
|
15
|
+
const actor = new TestActor("user-123", "business-456", "token-789");
|
|
16
|
+
|
|
17
|
+
expect(actor.id).toEqual("user-123");
|
|
18
|
+
expect(actor.businessId).toEqual("business-456");
|
|
19
|
+
expect(actor.token).toEqual("token-789");
|
|
20
|
+
expect(actor.permissions).toEqual([]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("Actor - set permissions", () => {
|
|
24
|
+
const actor = new TestActor("user-123", "business-456", "token-789");
|
|
25
|
+
|
|
26
|
+
const permissions: Permission[] = [
|
|
27
|
+
{ resource: "user", action: "create" },
|
|
28
|
+
{ resource: "user", action: "read" },
|
|
29
|
+
{ resource: "post", action: "write" },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
actor.permissions = permissions;
|
|
33
|
+
expect(actor.permissions.length).toEqual(3);
|
|
34
|
+
expect(actor.permissions[0].resource).toEqual("user");
|
|
35
|
+
expect(actor.permissions[0].action).toEqual("create");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("Actor - add single permission", () => {
|
|
39
|
+
const actor = new TestActor("user-123", "business-456", "token-789");
|
|
40
|
+
|
|
41
|
+
actor.addPermission({ resource: "user", action: "create" });
|
|
42
|
+
expect(actor.permissions.length).toEqual(1);
|
|
43
|
+
|
|
44
|
+
actor.addPermission({ resource: "user", action: "read" });
|
|
45
|
+
expect(actor.permissions.length).toEqual(2);
|
|
46
|
+
|
|
47
|
+
// Adding a new object with same values will add it (Set compares by reference)
|
|
48
|
+
actor.addPermission({ resource: "user", action: "create" });
|
|
49
|
+
expect(actor.permissions.length).toEqual(3);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("Actor - add multiple permissions", () => {
|
|
53
|
+
const actor = new TestActor("user-123", "business-456", "token-789");
|
|
54
|
+
|
|
55
|
+
const permissions: Permission[] = [
|
|
56
|
+
{ resource: "user", action: "create" },
|
|
57
|
+
{ resource: "user", action: "read" },
|
|
58
|
+
{ resource: "post", action: "write" },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
actor.addPermissions(permissions);
|
|
62
|
+
expect(actor.permissions.length).toEqual(3);
|
|
63
|
+
|
|
64
|
+
// Adding more permissions (new objects will be added even with same values)
|
|
65
|
+
actor.addPermissions([
|
|
66
|
+
{ resource: "comment", action: "delete" },
|
|
67
|
+
{ resource: "user", action: "create" }, // New object, will be added
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
expect(actor.permissions.length).toEqual(5);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("Actor - permissions setter replaces all", () => {
|
|
74
|
+
const actor = new TestActor("user-123", "business-456", "token-789");
|
|
75
|
+
|
|
76
|
+
// Set initial permissions
|
|
77
|
+
actor.permissions = [
|
|
78
|
+
{ resource: "user", action: "create" },
|
|
79
|
+
{ resource: "user", action: "read" },
|
|
80
|
+
];
|
|
81
|
+
expect(actor.permissions.length).toEqual(2);
|
|
82
|
+
|
|
83
|
+
// Replace with new set
|
|
84
|
+
actor.permissions = [
|
|
85
|
+
{ resource: "post", action: "write" },
|
|
86
|
+
];
|
|
87
|
+
expect(actor.permissions.length).toEqual(1);
|
|
88
|
+
expect(actor.permissions[0].resource).toEqual("post");
|
|
89
|
+
});
|