@flowerforce/flowerbase 1.7.6-beta.6 → 1.7.6-beta.8

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.
@@ -1,6 +1,6 @@
1
1
  import { ObjectId } from 'mongodb'
2
2
  import MongoDbAtlas from '..'
3
- import { Role, Rules } from '../../../features/rules/interface'
3
+ import { Rules } from '../../../features/rules/interface'
4
4
 
5
5
  const createAppWithCollection = (collection: Record<string, unknown>) => ({
6
6
  mongo: {
@@ -12,12 +12,12 @@ const createAppWithCollection = (collection: Record<string, unknown>) => ({
12
12
  }
13
13
  })
14
14
 
15
- const createRules = (roleOverrides: Partial<Role> = {}): Rules => ({
15
+ const createRules = (overrides: Partial<Rules[keyof Rules]> = {}): Rules => ({
16
16
  todos: {
17
17
  database: 'db',
18
18
  collection: 'todos',
19
- filters: [],
20
- roles: [
19
+ filters: overrides.filters ?? [],
20
+ roles: overrides.roles ?? [
21
21
  {
22
22
  name: 'owner',
23
23
  apply_when: {},
@@ -25,10 +25,10 @@ const createRules = (roleOverrides: Partial<Role> = {}): Rules => ({
25
25
  delete: true,
26
26
  search: true,
27
27
  read: true,
28
- write: true,
29
- ...roleOverrides
28
+ write: true
30
29
  }
31
- ]
30
+ ],
31
+ ...overrides
32
32
  }
33
33
  })
34
34
 
@@ -268,6 +268,204 @@ describe('mongodb-atlas Realm compatibility', () => {
268
268
  expect(findOne).toHaveBeenCalledWith({}, undefined)
269
269
  })
270
270
 
271
+ it('keeps write-only field permissions readable in findOne responses', async () => {
272
+ const doc = {
273
+ _id: new ObjectId(),
274
+ name: 'HQ',
275
+ address: 'Main Street 1'
276
+ }
277
+ const findOne = jest.fn().mockResolvedValue(doc)
278
+ const collection = {
279
+ collectionName: 'todos',
280
+ findOne
281
+ }
282
+ const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
283
+ rules: createRules({
284
+ roles: [
285
+ {
286
+ name: 'internal',
287
+ apply_when: {},
288
+ insert: true,
289
+ delete: true,
290
+ search: true,
291
+ read: true,
292
+ write: true,
293
+ fields: {
294
+ name: { write: true },
295
+ address: { write: true }
296
+ }
297
+ } as any
298
+ ]
299
+ }),
300
+ user: { id: 'user-1', custom_data: { type: 'internal' } }
301
+ }).db('db').collection('todos')
302
+
303
+ const result = await operators.findOne({ _id: doc._id })
304
+
305
+ expect(result).toEqual(doc)
306
+ })
307
+
308
+ it('keeps write-only field permissions readable in find responses', async () => {
309
+ const doc = {
310
+ _id: new ObjectId(),
311
+ name: 'HQ',
312
+ address: 'Main Street 1'
313
+ }
314
+ const cursor = {
315
+ toArray: jest.fn().mockResolvedValue([doc]),
316
+ sort: jest.fn(),
317
+ skip: jest.fn(),
318
+ limit: jest.fn()
319
+ }
320
+ const find = jest.fn().mockReturnValue(cursor)
321
+ const collection = {
322
+ collectionName: 'todos',
323
+ find
324
+ }
325
+ const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
326
+ rules: createRules({
327
+ roles: [
328
+ {
329
+ name: 'internal',
330
+ apply_when: {},
331
+ insert: true,
332
+ delete: true,
333
+ search: true,
334
+ read: true,
335
+ write: true,
336
+ fields: {
337
+ name: { write: true },
338
+ address: { write: true }
339
+ }
340
+ } as any
341
+ ]
342
+ }),
343
+ user: { id: 'user-1', custom_data: { type: 'internal' } }
344
+ }).db('db').collection('todos')
345
+
346
+ const result = await operators.find({ _id: doc._id }).toArray()
347
+
348
+ expect(result).toEqual([doc])
349
+ })
350
+
351
+ it('supports Realm-style field access where write implies read without explicit top-level read', async () => {
352
+ const doc = {
353
+ _id: new ObjectId(),
354
+ name: 'HQ',
355
+ address: 'Main Street 1',
356
+ city: 'Rome'
357
+ }
358
+ const findOne = jest.fn().mockResolvedValue(doc)
359
+ const collection = {
360
+ collectionName: 'todos',
361
+ findOne
362
+ }
363
+ const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
364
+ rules: createRules({
365
+ roles: [
366
+ {
367
+ name: 'Internal',
368
+ apply_when: { '%%user.custom_data.type': 'internal' },
369
+ fields: {
370
+ name: { write: true },
371
+ address: { write: true }
372
+ }
373
+ } as any
374
+ ]
375
+ }),
376
+ user: { id: 'user-1', custom_data: { type: 'internal' } }
377
+ }).db('db').collection('todos')
378
+
379
+ const result = await operators.findOne({ _id: doc._id })
380
+
381
+ expect(result).toEqual({
382
+ _id: doc._id,
383
+ name: 'HQ',
384
+ address: 'Main Street 1'
385
+ })
386
+ })
387
+
388
+ it('supports Realm-style field access with explicit field-level read without top-level read', async () => {
389
+ const doc = {
390
+ _id: new ObjectId(),
391
+ name: 'HQ',
392
+ address: 'Main Street 1',
393
+ city: 'Rome'
394
+ }
395
+ const findOne = jest.fn().mockResolvedValue(doc)
396
+ const collection = {
397
+ collectionName: 'todos',
398
+ findOne
399
+ }
400
+ const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
401
+ rules: createRules({
402
+ roles: [
403
+ {
404
+ name: 'Internal',
405
+ apply_when: { '%%user.custom_data.type': 'internal' },
406
+ fields: {
407
+ name: { read: true },
408
+ address: { read: true }
409
+ }
410
+ } as any
411
+ ]
412
+ }),
413
+ user: { id: 'user-1', custom_data: { type: 'internal' } }
414
+ }).db('db').collection('todos')
415
+
416
+ const result = await operators.findOne({ _id: doc._id })
417
+
418
+ expect(result).toEqual({
419
+ _id: doc._id,
420
+ name: 'HQ',
421
+ address: 'Main Street 1'
422
+ })
423
+ })
424
+
425
+ it('keeps top-level read access even when fields only define write rules', async () => {
426
+ const doc = {
427
+ _id: new ObjectId(),
428
+ userId: 'user-1',
429
+ email: 'owner@example.com',
430
+ workspaces: ['workspace-1'],
431
+ avatar: 'owner.png',
432
+ name: 'Owner name',
433
+ tags: ['owner'],
434
+ updatedAt: new Date('2026-03-17T10:00:00.000Z')
435
+ }
436
+ const findOne = jest.fn().mockResolvedValue(doc)
437
+ const collection = {
438
+ collectionName: 'todos',
439
+ findOne
440
+ }
441
+ const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
442
+ rules: createRules({
443
+ roles: [
444
+ {
445
+ name: 'all',
446
+ apply_when: { '%%true': true },
447
+ insert: false,
448
+ delete: false,
449
+ search: true,
450
+ read: true,
451
+ fields: {
452
+ avatar: { write: false },
453
+ name: { write: true },
454
+ tags: { write: false },
455
+ updatedAt: { write: true }
456
+ },
457
+ additional_fields: {}
458
+ } as any
459
+ ]
460
+ }),
461
+ user: { id: 'user-1', custom_data: { workspaces: ['workspace-1'] } }
462
+ }).db('db').collection('todos')
463
+
464
+ const result = await operators.findOne({ _id: doc._id })
465
+
466
+ expect(result).toEqual(doc)
467
+ })
468
+
271
469
  it('returns insertMany insertedIds as an array', async () => {
272
470
  const id0 = new ObjectId()
273
471
  const id1 = new ObjectId()
@@ -66,6 +66,33 @@ describe('MongoDB Atlas aggregate helpers', () => {
66
66
  expect(hidden).toEqual(expect.arrayContaining(['secret', 'hiddenExtra']))
67
67
  expect(hidden).not.toContain('visible')
68
68
  })
69
+
70
+ it('does not hide fields that are write-only in the rules config', () => {
71
+ const roles: Role[] = [
72
+ {
73
+ name: 'internal',
74
+ apply_when: {},
75
+ insert: true,
76
+ delete: true,
77
+ search: true,
78
+ read: true,
79
+ write: true,
80
+ fields: {
81
+ name: { write: true },
82
+ address: { write: true },
83
+ secret: { read: false, write: false }
84
+ }
85
+ }
86
+ ]
87
+
88
+ const hidden = getHiddenFieldsFromRulesConfig({
89
+ roles
90
+ })
91
+
92
+ expect(hidden).toContain('secret')
93
+ expect(hidden).not.toContain('name')
94
+ expect(hidden).not.toContain('address')
95
+ })
69
96
  })
70
97
 
71
98
  describe('prependUnsetStage', () => {