const { applyPerms } = require('../../../services/applyPerms'); const { sequelize, Permission, User } = require('../../../models'); const bcrypt = require('bcrypt'); describe('applyPerms', () => { let owner, target; beforeEach(async () => { await sequelize.query('DELETE FROM permissions'); const hash = await bcrypt.hash('pass', 10); owner = await User.create({ email: 'owner@test.com', password_digest: hash, }); target = await User.create({ email: 'target@test.com', password_digest: hash, }); }); it('should create a new permission on upsert when none exists', async () => { await sequelize.transaction(async (tx) => { await applyPerms(tx, { upserts: [ { userId: target.id, resourceType: 'project', resourceUid: 'proj-uid-1', accessLevel: 'ro', propagation: 'direct', grantedByUserId: owner.id, }, ], deletes: [], }); }); const perm = await Permission.findOne({ where: { user_id: target.id, resource_type: 'project', resource_uid: 'proj-uid-1', }, }); expect(perm).not.toBeNull(); expect(perm.access_level).toBe('ro'); expect(perm.propagation).toBe('direct'); expect(perm.granted_by_user_id).toBe(owner.id); }); it('should upgrade access level on upsert when permission already exists (ro -> rw)', async () => { // Create existing ro permission await Permission.create({ user_id: target.id, resource_type: 'project', resource_uid: 'proj-uid-2', access_level: 'ro', propagation: 'direct', granted_by_user_id: owner.id, }); await sequelize.transaction(async (tx) => { await applyPerms(tx, { upserts: [ { userId: target.id, resourceType: 'project', resourceUid: 'proj-uid-2', accessLevel: 'rw', grantedByUserId: owner.id, }, ], deletes: [], }); }); const perm = await Permission.findOne({ where: { user_id: target.id, resource_type: 'project', resource_uid: 'proj-uid-2', }, }); expect(perm.access_level).toBe('rw'); }); it('should keep rw when upserting ro on existing rw permission', async () => { await Permission.create({ user_id: target.id, resource_type: 'project', resource_uid: 'proj-uid-3', access_level: 'rw', propagation: 'direct', granted_by_user_id: owner.id, }); await sequelize.transaction(async (tx) => { await applyPerms(tx, { upserts: [ { userId: target.id, resourceType: 'project', resourceUid: 'proj-uid-3', accessLevel: 'ro', grantedByUserId: owner.id, }, ], deletes: [], }); }); const perm = await Permission.findOne({ where: { user_id: target.id, resource_type: 'project', resource_uid: 'proj-uid-3', }, }); expect(perm.access_level).toBe('rw'); }); it('should delete a permission', async () => { await Permission.create({ user_id: target.id, resource_type: 'task', resource_uid: 'task-uid-1', access_level: 'rw', propagation: 'direct', granted_by_user_id: owner.id, }); await sequelize.transaction(async (tx) => { await applyPerms(tx, { upserts: [], deletes: [ { userId: target.id, resourceType: 'task', resourceUid: 'task-uid-1', }, ], }); }); const perm = await Permission.findOne({ where: { user_id: target.id, resource_type: 'task', resource_uid: 'task-uid-1', }, }); expect(perm).toBeNull(); }); it('should handle empty upserts and deletes', async () => { await expect( sequelize.transaction(async (tx) => { await applyPerms(tx, { upserts: [], deletes: [] }); }) ).resolves.not.toThrow(); }); it('should handle multiple upserts and deletes in one call', async () => { await Permission.create({ user_id: target.id, resource_type: 'note', resource_uid: 'note-to-delete', access_level: 'ro', propagation: 'direct', granted_by_user_id: owner.id, }); await sequelize.transaction(async (tx) => { await applyPerms(tx, { upserts: [ { userId: target.id, resourceType: 'project', resourceUid: 'new-proj', accessLevel: 'rw', propagation: 'direct', grantedByUserId: owner.id, }, { userId: target.id, resourceType: 'task', resourceUid: 'new-task', accessLevel: 'ro', propagation: 'inherited', grantedByUserId: owner.id, }, ], deletes: [ { userId: target.id, resourceType: 'note', resourceUid: 'note-to-delete', }, ], }); }); const projPerm = await Permission.findOne({ where: { user_id: target.id, resource_uid: 'new-proj' }, }); const taskPerm = await Permission.findOne({ where: { user_id: target.id, resource_uid: 'new-task' }, }); const notePerm = await Permission.findOne({ where: { user_id: target.id, resource_uid: 'note-to-delete' }, }); expect(projPerm).not.toBeNull(); expect(projPerm.access_level).toBe('rw'); expect(taskPerm).not.toBeNull(); expect(taskPerm.access_level).toBe('ro'); expect(taskPerm.propagation).toBe('inherited'); expect(notePerm).toBeNull(); }); it('should set default propagation to direct when not specified', async () => { await sequelize.transaction(async (tx) => { await applyPerms(tx, { upserts: [ { userId: target.id, resourceType: 'project', resourceUid: 'proj-default', accessLevel: 'ro', grantedByUserId: owner.id, // no propagation }, ], deletes: [], }); }); const perm = await Permission.findOne({ where: { user_id: target.id, resource_uid: 'proj-default' }, }); expect(perm.propagation).toBe('direct'); }); it('should not fail when deleting a non-existent permission', async () => { await expect( sequelize.transaction(async (tx) => { await applyPerms(tx, { upserts: [], deletes: [ { userId: target.id, resourceType: 'project', resourceUid: 'does-not-exist', }, ], }); }) ).resolves.not.toThrow(); }); });