@geekmidas/audit 9.0.2 → 10.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +8 -5
- package/CHANGELOG.md +0 -101
- package/TECHNICAL.md +0 -937
- package/src/Auditor.ts +0 -157
- package/src/DefaultAuditor.ts +0 -141
- package/src/__tests__/CacheAuditStorage.spec.ts +0 -382
- package/src/__tests__/DefaultAuditor.spec.ts +0 -529
- package/src/__tests__/InMemoryAuditStorage.spec.ts +0 -317
- package/src/__tests__/KnexAuditStorage.integration.spec.ts +0 -739
- package/src/__tests__/KyselyAuditStorage.integration.spec.ts +0 -518
- package/src/__tests__/KyselyAuditStorage.spec.ts +0 -443
- package/src/cache.ts +0 -263
- package/src/index.ts +0 -23
- package/src/knex.ts +0 -430
- package/src/kysely.ts +0 -424
- package/src/memory.ts +0 -50
- package/src/storage.ts +0 -203
- package/src/types.ts +0 -154
- package/tsconfig.json +0 -9
|
@@ -1,518 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CamelCasePlugin,
|
|
3
|
-
type Generated,
|
|
4
|
-
Kysely,
|
|
5
|
-
PostgresDialect,
|
|
6
|
-
sql,
|
|
7
|
-
} from 'kysely';
|
|
8
|
-
import pg from 'pg';
|
|
9
|
-
import {
|
|
10
|
-
afterAll,
|
|
11
|
-
afterEach,
|
|
12
|
-
beforeAll,
|
|
13
|
-
beforeEach,
|
|
14
|
-
describe,
|
|
15
|
-
expect,
|
|
16
|
-
it,
|
|
17
|
-
} from 'vitest';
|
|
18
|
-
import { TEST_DATABASE_CONFIG } from '../../../testkit/test/globalSetup';
|
|
19
|
-
import { DefaultAuditor } from '../DefaultAuditor';
|
|
20
|
-
import { type AuditLogTable, KyselyAuditStorage } from '../kysely';
|
|
21
|
-
import type { AuditableAction } from '../types';
|
|
22
|
-
|
|
23
|
-
// Use unique table names to avoid conflicts with parallel tests
|
|
24
|
-
const AUDIT_TABLE = 'audit_int_logs' as const;
|
|
25
|
-
const USERS_TABLE = 'audit_int_users' as const;
|
|
26
|
-
|
|
27
|
-
interface TestDatabase {
|
|
28
|
-
[AUDIT_TABLE]: AuditLogTable;
|
|
29
|
-
[USERS_TABLE]: {
|
|
30
|
-
id: Generated<number>;
|
|
31
|
-
name: string;
|
|
32
|
-
email: string;
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Define test audit actions
|
|
37
|
-
type TestAuditAction =
|
|
38
|
-
| AuditableAction<'user.created', { userId: number; email: string }>
|
|
39
|
-
| AuditableAction<'user.updated', { userId: number; changes: string[] }>
|
|
40
|
-
| AuditableAction<'user.deleted', { userId: number }>;
|
|
41
|
-
|
|
42
|
-
describe('KyselyAuditStorage Integration Tests', () => {
|
|
43
|
-
let db: Kysely<TestDatabase>;
|
|
44
|
-
let storage: KyselyAuditStorage<TestDatabase>;
|
|
45
|
-
|
|
46
|
-
beforeAll(async () => {
|
|
47
|
-
db = new Kysely<TestDatabase>({
|
|
48
|
-
dialect: new PostgresDialect({
|
|
49
|
-
pool: new pg.Pool({
|
|
50
|
-
...TEST_DATABASE_CONFIG,
|
|
51
|
-
database: 'postgres',
|
|
52
|
-
}),
|
|
53
|
-
}),
|
|
54
|
-
plugins: [new CamelCasePlugin()],
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
// Create audit_logs table
|
|
58
|
-
await db.schema
|
|
59
|
-
.createTable(AUDIT_TABLE)
|
|
60
|
-
.ifNotExists()
|
|
61
|
-
.addColumn('id', 'varchar(32)', (col) => col.primaryKey())
|
|
62
|
-
.addColumn('type', 'varchar', (col) => col.notNull())
|
|
63
|
-
.addColumn('operation', 'varchar', (col) => col.notNull())
|
|
64
|
-
.addColumn('table', 'varchar')
|
|
65
|
-
.addColumn('entityId', 'varchar')
|
|
66
|
-
.addColumn('oldValues', 'jsonb')
|
|
67
|
-
.addColumn('newValues', 'jsonb')
|
|
68
|
-
.addColumn('payload', 'jsonb')
|
|
69
|
-
.addColumn('timestamp', 'timestamp', (col) =>
|
|
70
|
-
col.defaultTo(sql`now()`).notNull(),
|
|
71
|
-
)
|
|
72
|
-
.addColumn('actorId', 'varchar')
|
|
73
|
-
.addColumn('actorType', 'varchar')
|
|
74
|
-
.addColumn('actorData', 'jsonb')
|
|
75
|
-
.addColumn('metadata', 'jsonb')
|
|
76
|
-
.execute();
|
|
77
|
-
|
|
78
|
-
// Create users table for testing audit integration
|
|
79
|
-
await db.schema
|
|
80
|
-
.createTable(USERS_TABLE)
|
|
81
|
-
.ifNotExists()
|
|
82
|
-
.addColumn('id', 'serial', (col) => col.primaryKey())
|
|
83
|
-
.addColumn('name', 'varchar', (col) => col.notNull())
|
|
84
|
-
.addColumn('email', 'varchar', (col) => col.notNull().unique())
|
|
85
|
-
.execute();
|
|
86
|
-
|
|
87
|
-
storage = new KyselyAuditStorage({
|
|
88
|
-
db,
|
|
89
|
-
tableName: AUDIT_TABLE,
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
afterEach(async () => {
|
|
94
|
-
// Clean up data after each test
|
|
95
|
-
await db.deleteFrom(AUDIT_TABLE).execute();
|
|
96
|
-
await db.deleteFrom(USERS_TABLE).execute();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
afterAll(async () => {
|
|
100
|
-
// Drop tables and close connection
|
|
101
|
-
await db.schema.dropTable(AUDIT_TABLE).ifExists().execute();
|
|
102
|
-
await db.schema.dropTable(USERS_TABLE).ifExists().execute();
|
|
103
|
-
await db.destroy();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
describe('write', () => {
|
|
107
|
-
it('should write audit records to database', async () => {
|
|
108
|
-
const auditor = new DefaultAuditor<TestAuditAction>({
|
|
109
|
-
actor: { id: 'user-123', type: 'user' },
|
|
110
|
-
storage,
|
|
111
|
-
metadata: { requestId: 'req-456' },
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
auditor.audit('user.created', { userId: 1, email: 'test@example.com' });
|
|
115
|
-
|
|
116
|
-
await auditor.flush();
|
|
117
|
-
|
|
118
|
-
// Verify record was written
|
|
119
|
-
const records = await db.selectFrom(AUDIT_TABLE).selectAll().execute();
|
|
120
|
-
|
|
121
|
-
expect(records).toHaveLength(1);
|
|
122
|
-
expect(records[0].type).toBe('user.created');
|
|
123
|
-
expect(records[0].actorId).toBe('user-123');
|
|
124
|
-
expect(records[0].actorType).toBe('user');
|
|
125
|
-
expect(records[0].payload).toEqual({
|
|
126
|
-
userId: 1,
|
|
127
|
-
email: 'test@example.com',
|
|
128
|
-
});
|
|
129
|
-
expect(records[0].metadata).toEqual({
|
|
130
|
-
requestId: 'req-456',
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('should write multiple audit records in batch', async () => {
|
|
135
|
-
const auditor = new DefaultAuditor<TestAuditAction>({
|
|
136
|
-
actor: { id: 'admin-1', type: 'admin' },
|
|
137
|
-
storage,
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
auditor.audit('user.created', { userId: 1, email: 'user1@example.com' });
|
|
141
|
-
auditor.audit('user.created', { userId: 2, email: 'user2@example.com' });
|
|
142
|
-
auditor.audit('user.updated', { userId: 1, changes: ['name'] });
|
|
143
|
-
|
|
144
|
-
await auditor.flush();
|
|
145
|
-
|
|
146
|
-
const records = await db
|
|
147
|
-
.selectFrom(AUDIT_TABLE)
|
|
148
|
-
.selectAll()
|
|
149
|
-
.orderBy('timestamp', 'asc')
|
|
150
|
-
.execute();
|
|
151
|
-
|
|
152
|
-
expect(records).toHaveLength(3);
|
|
153
|
-
expect(records.map((r) => r.type)).toEqual([
|
|
154
|
-
'user.created',
|
|
155
|
-
'user.created',
|
|
156
|
-
'user.updated',
|
|
157
|
-
]);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('should write audit records within transaction', async () => {
|
|
161
|
-
const auditor = new DefaultAuditor<TestAuditAction>({
|
|
162
|
-
actor: { id: 'user-123', type: 'user' },
|
|
163
|
-
storage,
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
await db.transaction().execute(async (trx) => {
|
|
167
|
-
// Insert user
|
|
168
|
-
const user = await trx
|
|
169
|
-
.insertInto(USERS_TABLE)
|
|
170
|
-
.values({ name: 'Test User', email: 'test@example.com' })
|
|
171
|
-
.returningAll()
|
|
172
|
-
.executeTakeFirstOrThrow();
|
|
173
|
-
|
|
174
|
-
// Audit the creation
|
|
175
|
-
auditor.audit(
|
|
176
|
-
'user.created',
|
|
177
|
-
{ userId: user.id, email: user.email },
|
|
178
|
-
{ entityId: String(user.id), table: 'users', operation: 'INSERT' },
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
// Flush within transaction
|
|
182
|
-
await auditor.flush(trx);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// Verify both user and audit record exist
|
|
186
|
-
const users = await db.selectFrom(USERS_TABLE).selectAll().execute();
|
|
187
|
-
const audits = await db.selectFrom(AUDIT_TABLE).selectAll().execute();
|
|
188
|
-
|
|
189
|
-
expect(users).toHaveLength(1);
|
|
190
|
-
expect(audits).toHaveLength(1);
|
|
191
|
-
expect(audits[0].entityId).toBe(String(users[0].id));
|
|
192
|
-
expect(audits[0].table).toBe('users');
|
|
193
|
-
expect(audits[0].operation).toBe('INSERT');
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it('should rollback audit records when transaction fails', async () => {
|
|
197
|
-
const auditor = new DefaultAuditor<TestAuditAction>({
|
|
198
|
-
actor: { id: 'user-123', type: 'user' },
|
|
199
|
-
storage,
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
const transactionPromise = db.transaction().execute(async (trx) => {
|
|
203
|
-
// Insert user
|
|
204
|
-
const user = await trx
|
|
205
|
-
.insertInto(USERS_TABLE)
|
|
206
|
-
.values({ name: 'Rollback User', email: 'rollback@example.com' })
|
|
207
|
-
.returningAll()
|
|
208
|
-
.executeTakeFirstOrThrow();
|
|
209
|
-
|
|
210
|
-
// Audit the creation
|
|
211
|
-
auditor.audit('user.created', { userId: user.id, email: user.email });
|
|
212
|
-
|
|
213
|
-
// Flush within transaction
|
|
214
|
-
await auditor.flush(trx);
|
|
215
|
-
|
|
216
|
-
// Throw error to rollback
|
|
217
|
-
throw new Error('Transaction should rollback');
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
await expect(transactionPromise).rejects.toThrow(
|
|
221
|
-
'Transaction should rollback',
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
// Verify both user and audit record were rolled back
|
|
225
|
-
const users = await db.selectFrom(USERS_TABLE).selectAll().execute();
|
|
226
|
-
const audits = await db.selectFrom(AUDIT_TABLE).selectAll().execute();
|
|
227
|
-
|
|
228
|
-
expect(users).toHaveLength(0);
|
|
229
|
-
expect(audits).toHaveLength(0);
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
describe('query', () => {
|
|
234
|
-
beforeEach(async () => {
|
|
235
|
-
// Insert test audit records
|
|
236
|
-
const auditor1 = new DefaultAuditor<TestAuditAction>({
|
|
237
|
-
actor: { id: 'user-1', type: 'user' },
|
|
238
|
-
storage,
|
|
239
|
-
metadata: { endpoint: '/users' },
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
const auditor2 = new DefaultAuditor<TestAuditAction>({
|
|
243
|
-
actor: { id: 'admin-1', type: 'admin' },
|
|
244
|
-
storage,
|
|
245
|
-
metadata: { endpoint: '/admin/users' },
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
auditor1.audit(
|
|
249
|
-
'user.created',
|
|
250
|
-
{ userId: 1, email: 'user1@example.com' },
|
|
251
|
-
{ entityId: '1', table: 'users' },
|
|
252
|
-
);
|
|
253
|
-
auditor1.audit(
|
|
254
|
-
'user.updated',
|
|
255
|
-
{ userId: 1, changes: ['name'] },
|
|
256
|
-
{ entityId: '1', table: 'users' },
|
|
257
|
-
);
|
|
258
|
-
auditor2.audit(
|
|
259
|
-
'user.deleted',
|
|
260
|
-
{ userId: 2 },
|
|
261
|
-
{ entityId: '2', table: 'users' },
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
await auditor1.flush();
|
|
265
|
-
await auditor2.flush();
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it('should query all records', async () => {
|
|
269
|
-
const records = await storage.query({});
|
|
270
|
-
|
|
271
|
-
expect(records).toHaveLength(3);
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it('should filter by type', async () => {
|
|
275
|
-
const records = await storage.query({ type: 'user.created' });
|
|
276
|
-
|
|
277
|
-
expect(records).toHaveLength(1);
|
|
278
|
-
expect(records[0].type).toBe('user.created');
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it('should filter by multiple types', async () => {
|
|
282
|
-
const records = await storage.query({
|
|
283
|
-
type: ['user.created', 'user.updated'],
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
expect(records).toHaveLength(2);
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
it('should filter by actorId', async () => {
|
|
290
|
-
const records = await storage.query({ actorId: 'admin-1' });
|
|
291
|
-
|
|
292
|
-
expect(records).toHaveLength(1);
|
|
293
|
-
expect(records[0].type).toBe('user.deleted');
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
it('should filter by entityId', async () => {
|
|
297
|
-
const records = await storage.query({ entityId: '1' });
|
|
298
|
-
|
|
299
|
-
expect(records).toHaveLength(2);
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
it('should filter by table', async () => {
|
|
303
|
-
const records = await storage.query({ table: 'users' });
|
|
304
|
-
|
|
305
|
-
expect(records).toHaveLength(3);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
it('should apply pagination', async () => {
|
|
309
|
-
const page1 = await storage.query({ limit: 2, offset: 0 });
|
|
310
|
-
const page2 = await storage.query({ limit: 2, offset: 2 });
|
|
311
|
-
|
|
312
|
-
expect(page1).toHaveLength(2);
|
|
313
|
-
expect(page2).toHaveLength(1);
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
it('should order by timestamp descending by default', async () => {
|
|
317
|
-
const records = await storage.query({});
|
|
318
|
-
|
|
319
|
-
// Records should be in descending order (newest first)
|
|
320
|
-
for (let i = 1; i < records.length; i++) {
|
|
321
|
-
expect(records[i - 1].timestamp.getTime()).toBeGreaterThanOrEqual(
|
|
322
|
-
records[i].timestamp.getTime(),
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it('should order by type ascending', async () => {
|
|
328
|
-
const records = await storage.query({
|
|
329
|
-
orderBy: 'type',
|
|
330
|
-
orderDirection: 'asc',
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
const types = records.map((r) => r.type);
|
|
334
|
-
expect(types).toEqual([...types].sort());
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
describe('count', () => {
|
|
339
|
-
beforeEach(async () => {
|
|
340
|
-
const auditor = new DefaultAuditor<TestAuditAction>({
|
|
341
|
-
actor: { id: 'user-1', type: 'user' },
|
|
342
|
-
storage,
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
auditor.audit('user.created', { userId: 1, email: 'user1@example.com' });
|
|
346
|
-
auditor.audit('user.created', { userId: 2, email: 'user2@example.com' });
|
|
347
|
-
auditor.audit('user.updated', { userId: 1, changes: ['name'] });
|
|
348
|
-
|
|
349
|
-
await auditor.flush();
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it('should count all records', async () => {
|
|
353
|
-
const count = await storage.count({});
|
|
354
|
-
|
|
355
|
-
expect(count).toBe(3);
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
it('should count with type filter', async () => {
|
|
359
|
-
const count = await storage.count({ type: 'user.created' });
|
|
360
|
-
|
|
361
|
-
expect(count).toBe(2);
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it('should count with actorId filter', async () => {
|
|
365
|
-
const count = await storage.count({ actorId: 'user-1' });
|
|
366
|
-
|
|
367
|
-
expect(count).toBe(3);
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
describe('complex scenarios', () => {
|
|
372
|
-
it('should handle audit trail for user lifecycle', async () => {
|
|
373
|
-
// Create user
|
|
374
|
-
const createAuditor = new DefaultAuditor<TestAuditAction>({
|
|
375
|
-
actor: { id: 'admin-1', type: 'admin' },
|
|
376
|
-
storage,
|
|
377
|
-
metadata: { endpoint: '/users', method: 'POST' },
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
const user = await db
|
|
381
|
-
.insertInto(USERS_TABLE)
|
|
382
|
-
.values({ name: 'John Doe', email: 'john@example.com' })
|
|
383
|
-
.returningAll()
|
|
384
|
-
.executeTakeFirstOrThrow();
|
|
385
|
-
|
|
386
|
-
createAuditor.audit(
|
|
387
|
-
'user.created',
|
|
388
|
-
{ userId: user.id, email: user.email },
|
|
389
|
-
{
|
|
390
|
-
entityId: String(user.id),
|
|
391
|
-
table: 'users',
|
|
392
|
-
operation: 'INSERT',
|
|
393
|
-
newValues: { name: 'John Doe', email: 'john@example.com' },
|
|
394
|
-
},
|
|
395
|
-
);
|
|
396
|
-
await createAuditor.flush();
|
|
397
|
-
|
|
398
|
-
// Update user
|
|
399
|
-
const updateAuditor = new DefaultAuditor<TestAuditAction>({
|
|
400
|
-
actor: { id: 'user-1', type: 'user' },
|
|
401
|
-
storage,
|
|
402
|
-
metadata: { endpoint: '/users/1', method: 'PATCH' },
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
await db
|
|
406
|
-
.updateTable(USERS_TABLE)
|
|
407
|
-
.set({ name: 'John Smith' })
|
|
408
|
-
.where('id', '=', user.id)
|
|
409
|
-
.execute();
|
|
410
|
-
|
|
411
|
-
updateAuditor.audit(
|
|
412
|
-
'user.updated',
|
|
413
|
-
{ userId: user.id, changes: ['name'] },
|
|
414
|
-
{
|
|
415
|
-
entityId: String(user.id),
|
|
416
|
-
table: 'users',
|
|
417
|
-
operation: 'UPDATE',
|
|
418
|
-
oldValues: { name: 'John Doe' },
|
|
419
|
-
newValues: { name: 'John Smith' },
|
|
420
|
-
},
|
|
421
|
-
);
|
|
422
|
-
await updateAuditor.flush();
|
|
423
|
-
|
|
424
|
-
// Delete user
|
|
425
|
-
const deleteAuditor = new DefaultAuditor<TestAuditAction>({
|
|
426
|
-
actor: { id: 'admin-1', type: 'admin' },
|
|
427
|
-
storage,
|
|
428
|
-
metadata: { endpoint: '/users/1', method: 'DELETE' },
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
await db.deleteFrom(USERS_TABLE).where('id', '=', user.id).execute();
|
|
432
|
-
|
|
433
|
-
deleteAuditor.audit(
|
|
434
|
-
'user.deleted',
|
|
435
|
-
{ userId: user.id },
|
|
436
|
-
{
|
|
437
|
-
entityId: String(user.id),
|
|
438
|
-
table: 'users',
|
|
439
|
-
operation: 'DELETE',
|
|
440
|
-
oldValues: { name: 'John Smith', email: 'john@example.com' },
|
|
441
|
-
},
|
|
442
|
-
);
|
|
443
|
-
await deleteAuditor.flush();
|
|
444
|
-
|
|
445
|
-
// Query audit trail for this user
|
|
446
|
-
const auditTrail = await storage.query({
|
|
447
|
-
entityId: String(user.id),
|
|
448
|
-
orderDirection: 'asc',
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
expect(auditTrail).toHaveLength(3);
|
|
452
|
-
expect(auditTrail.map((r) => r.type)).toEqual([
|
|
453
|
-
'user.created',
|
|
454
|
-
'user.updated',
|
|
455
|
-
'user.deleted',
|
|
456
|
-
]);
|
|
457
|
-
expect(auditTrail.map((r) => r.operation)).toEqual([
|
|
458
|
-
'INSERT',
|
|
459
|
-
'UPDATE',
|
|
460
|
-
'DELETE',
|
|
461
|
-
]);
|
|
462
|
-
|
|
463
|
-
// Verify old/new values tracking
|
|
464
|
-
expect(auditTrail[1].oldValues).toEqual({ name: 'John Doe' });
|
|
465
|
-
expect(auditTrail[1].newValues).toEqual({ name: 'John Smith' });
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
it('should handle complex entity IDs', async () => {
|
|
469
|
-
const auditor = new DefaultAuditor<TestAuditAction>({
|
|
470
|
-
actor: { id: 'user-1', type: 'user' },
|
|
471
|
-
storage,
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
// Record with composite entity ID
|
|
475
|
-
auditor.record({
|
|
476
|
-
type: 'relation.created',
|
|
477
|
-
operation: 'INSERT',
|
|
478
|
-
entityId: { userId: 1, roleId: 'admin' },
|
|
479
|
-
table: 'user_roles',
|
|
480
|
-
payload: { granted: true },
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
await auditor.flush();
|
|
484
|
-
|
|
485
|
-
// Query by composite entity ID
|
|
486
|
-
const records = await storage.query({
|
|
487
|
-
entityId: { userId: 1, roleId: 'admin' },
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
expect(records).toHaveLength(1);
|
|
491
|
-
expect(records[0].entityId).toEqual({ userId: 1, roleId: 'admin' });
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
it('should preserve actor extra properties', async () => {
|
|
495
|
-
const auditor = new DefaultAuditor<TestAuditAction>({
|
|
496
|
-
actor: {
|
|
497
|
-
id: 'user-1',
|
|
498
|
-
type: 'user',
|
|
499
|
-
email: 'user@example.com',
|
|
500
|
-
roles: ['admin', 'editor'],
|
|
501
|
-
},
|
|
502
|
-
storage,
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
auditor.audit('user.created', { userId: 1, email: 'new@example.com' });
|
|
506
|
-
await auditor.flush();
|
|
507
|
-
|
|
508
|
-
const records = await storage.query({});
|
|
509
|
-
|
|
510
|
-
expect(records[0].actor).toEqual({
|
|
511
|
-
id: 'user-1',
|
|
512
|
-
type: 'user',
|
|
513
|
-
email: 'user@example.com',
|
|
514
|
-
roles: ['admin', 'editor'],
|
|
515
|
-
});
|
|
516
|
-
});
|
|
517
|
-
});
|
|
518
|
-
});
|