/* * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import { ConfigModule } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { DataSource, EntityManager, Repository } from 'typeorm'; import { AuthToken } from '../auth/auth-token.entity'; import { Author } from '../authors/author.entity'; import appConfigMock from '../config/mock/app.config.mock'; import authConfigMock from '../config/mock/auth.config.mock'; import databaseConfigMock from '../config/mock/database.config.mock'; import noteConfigMock from '../config/mock/note.config.mock'; import { PermissionsUpdateInconsistentError } from '../errors/errors'; import { Group } from '../groups/group.entity'; import { GroupsModule } from '../groups/groups.module'; import { SpecialGroup } from '../groups/groups.special'; import { Identity } from '../identity/identity.entity'; import { LoggerModule } from '../logger/logger.module'; import { Alias } from '../notes/alias.entity'; import { NoteGroupPermissionUpdateDto, NoteUserPermissionUpdateDto, } from '../notes/note-permissions.dto'; import { Note } from '../notes/note.entity'; import { NotesModule } from '../notes/notes.module'; import { Tag } from '../notes/tag.entity'; import { Edit } from '../revisions/edit.entity'; import { Revision } from '../revisions/revision.entity'; import { Session } from '../users/session.entity'; import { User } from '../users/user.entity'; import { UsersModule } from '../users/users.module'; import { NoteGroupPermission } from './note-group-permission.entity'; import { NoteUserPermission } from './note-user-permission.entity'; import { PermissionsModule } from './permissions.module'; import { GuestPermission, PermissionsService } from './permissions.service'; describe('PermissionsService', () => { let service: PermissionsService; let notes: Note[]; let noteRepo: Repository; let userRepo: Repository; let groupRepo: Repository; beforeAll(async () => { /** * We need to have *one* userRepo and *one* noteRepo for both the providers * array and the overrideProvider call, as otherwise we have two instances * and the mock of createQueryBuilder replaces the wrong one * **/ userRepo = new Repository( '', new EntityManager( new DataSource({ type: 'sqlite', database: ':memory:', }), ), undefined, ); noteRepo = new Repository( '', new EntityManager( new DataSource({ type: 'sqlite', database: ':memory:', }), ), undefined, ); const module: TestingModule = await Test.createTestingModule({ providers: [ PermissionsService, { provide: getRepositoryToken(Note), useValue: noteRepo, }, { provide: getRepositoryToken(Group), useClass: Repository, }, { provide: getRepositoryToken(User), useValue: userRepo, }, ], imports: [ LoggerModule, PermissionsModule, UsersModule, NotesModule, ConfigModule.forRoot({ isGlobal: true, load: [ appConfigMock, databaseConfigMock, authConfigMock, noteConfigMock, ], }), GroupsModule, ], }) .overrideProvider(getRepositoryToken(User)) .useValue(userRepo) .overrideProvider(getRepositoryToken(AuthToken)) .useValue({}) .overrideProvider(getRepositoryToken(Identity)) .useValue({}) .overrideProvider(getRepositoryToken(Edit)) .useValue({}) .overrideProvider(getRepositoryToken(Revision)) .useValue({}) .overrideProvider(getRepositoryToken(Note)) .useValue(noteRepo) .overrideProvider(getRepositoryToken(Tag)) .useValue({}) .overrideProvider(getRepositoryToken(NoteGroupPermission)) .useValue({}) .overrideProvider(getRepositoryToken(NoteUserPermission)) .useValue({}) .overrideProvider(getRepositoryToken(Group)) .useClass(Repository) .overrideProvider(getRepositoryToken(Session)) .useValue({}) .overrideProvider(getRepositoryToken(Author)) .useValue({}) .overrideProvider(getRepositoryToken(Alias)) .useValue({}) .compile(); service = module.get(PermissionsService); notes = await createNoteUserPermissionNotes(); groupRepo = module.get>(getRepositoryToken(Group)); noteRepo = module.get>(getRepositoryToken(Note)); }); // The two users we test with: const user2 = {} as User; user2.id = 2; const user1 = {} as User; user1.id = 1; it('should be defined', () => { expect(service).toBeDefined(); }); function createNote(owner: User): Note { const note = {} as Note; note.userPermissions = Promise.resolve([]); note.groupPermissions = Promise.resolve([]); note.owner = Promise.resolve(owner); return note; } /* * Creates the permission objects for UserPermission for two users with write and with out write permission */ async function createNoteUserPermissionNotes(): Promise { const note0 = createNote(user1); const note1 = createNote(user2); const note2 = createNote(user2); const note3 = createNote(user2); const note4 = createNote(user2); const note5 = createNote(user2); const note6 = createNote(user2); const note7 = createNote(user2); const noteUserPermission1 = {} as NoteUserPermission; noteUserPermission1.user = Promise.resolve(user1); const noteUserPermission2 = {} as NoteUserPermission; noteUserPermission2.user = Promise.resolve(user2); const noteUserPermission3 = {} as NoteUserPermission; noteUserPermission3.user = Promise.resolve(user1); noteUserPermission3.canEdit = true; const noteUserPermission4 = {} as NoteUserPermission; noteUserPermission4.user = Promise.resolve(user2); noteUserPermission4.canEdit = true; (await note1.userPermissions).push(noteUserPermission1); (await note2.userPermissions).push(noteUserPermission1); (await note2.userPermissions).push(noteUserPermission2); (await note3.userPermissions).push(noteUserPermission2); (await note3.userPermissions).push(noteUserPermission1); (await note4.userPermissions).push(noteUserPermission3); (await note5.userPermissions).push(noteUserPermission3); (await note5.userPermissions).push(noteUserPermission4); (await note6.userPermissions).push(noteUserPermission4); (await note6.userPermissions).push(noteUserPermission3); (await note7.userPermissions).push(noteUserPermission2); const everybody = {} as Group; everybody.name = SpecialGroup.EVERYONE; everybody.special = true; const noteEverybodyRead = createNote(user1); const noteGroupPermissionRead = {} as NoteGroupPermission; noteGroupPermissionRead.group = Promise.resolve(everybody); noteGroupPermissionRead.canEdit = false; noteGroupPermissionRead.note = Promise.resolve(noteEverybodyRead); noteEverybodyRead.groupPermissions = Promise.resolve([ noteGroupPermissionRead, ]); const noteEverybodyWrite = createNote(user1); const noteGroupPermissionWrite = {} as NoteGroupPermission; noteGroupPermissionWrite.group = Promise.resolve(everybody); noteGroupPermissionWrite.canEdit = true; noteGroupPermissionWrite.note = Promise.resolve(noteEverybodyWrite); noteEverybodyWrite.groupPermissions = Promise.resolve([ noteGroupPermissionWrite, ]); return [ note0, note1, note2, note3, note4, note5, note6, note7, noteEverybodyRead, noteEverybodyWrite, ]; } describe('mayRead works with', () => { it('Owner', async () => { service.guestPermission = GuestPermission.DENY; expect(await service.mayRead(user1, notes[0])).toBeTruthy(); expect(await service.mayRead(user1, notes[7])).toBeFalsy(); }); it('userPermission read', async () => { service.guestPermission = GuestPermission.DENY; expect(await service.mayRead(user1, notes[1])).toBeTruthy(); expect(await service.mayRead(user1, notes[2])).toBeTruthy(); expect(await service.mayRead(user1, notes[3])).toBeTruthy(); }); it('userPermission write', async () => { service.guestPermission = GuestPermission.DENY; expect(await service.mayRead(user1, notes[4])).toBeTruthy(); expect(await service.mayRead(user1, notes[5])).toBeTruthy(); expect(await service.mayRead(user1, notes[6])).toBeTruthy(); expect(await service.mayRead(user1, notes[7])).toBeFalsy(); }); describe('guest permission', () => { it('CREATE_ALIAS', async () => { service.guestPermission = GuestPermission.CREATE_ALIAS; expect(await service.mayRead(null, notes[8])).toBeTruthy(); }); it('CREATE', async () => { service.guestPermission = GuestPermission.CREATE; expect(await service.mayRead(null, notes[8])).toBeTruthy(); }); it('WRITE', async () => { service.guestPermission = GuestPermission.WRITE; expect(await service.mayRead(null, notes[8])).toBeTruthy(); }); it('READ', async () => { service.guestPermission = GuestPermission.READ; expect(await service.mayRead(null, notes[8])).toBeTruthy(); }); }); }); describe('mayWrite works with', () => { it('Owner', async () => { service.guestPermission = GuestPermission.DENY; expect(await service.mayWrite(user1, notes[0])).toBeTruthy(); expect(await service.mayWrite(user1, notes[7])).toBeFalsy(); }); it('userPermission read', async () => { service.guestPermission = GuestPermission.DENY; expect(await service.mayWrite(user1, notes[1])).toBeFalsy(); expect(await service.mayWrite(user1, notes[2])).toBeFalsy(); expect(await service.mayWrite(user1, notes[3])).toBeFalsy(); }); it('userPermission write', async () => { service.guestPermission = GuestPermission.DENY; expect(await service.mayWrite(user1, notes[4])).toBeTruthy(); expect(await service.mayWrite(user1, notes[5])).toBeTruthy(); expect(await service.mayWrite(user1, notes[6])).toBeTruthy(); expect(await service.mayWrite(user1, notes[7])).toBeFalsy(); }); describe('guest permission', () => { it('CREATE_ALIAS', async () => { service.guestPermission = GuestPermission.CREATE_ALIAS; expect(await service.mayWrite(null, notes[9])).toBeTruthy(); }); it('CREATE', async () => { service.guestPermission = GuestPermission.CREATE; expect(await service.mayWrite(null, notes[9])).toBeTruthy(); }); it('WRITE', async () => { service.guestPermission = GuestPermission.WRITE; expect(await service.mayWrite(null, notes[9])).toBeTruthy(); }); it('READ', async () => { service.guestPermission = GuestPermission.READ; expect(await service.mayWrite(null, notes[9])).toBeFalsy(); }); }); }); /* * Helper Object that arranges a list of GroupPermissions and if they allow a user to read or write a particular note. */ class NoteGroupPermissionWithResultForUser { permissions: NoteGroupPermission[]; allowsRead: boolean; allowsWrite: boolean; } /* * Setup function to create all the groups we use in the tests. */ function createGroups(): { [id: string]: Group } { const result: { [id: string]: Group } = {}; const everybody: Group = Group.create( SpecialGroup.EVERYONE, SpecialGroup.EVERYONE, true, ) as Group; result[SpecialGroup.EVERYONE] = everybody; const loggedIn = Group.create( SpecialGroup.LOGGED_IN, SpecialGroup.LOGGED_IN, true, ) as Group; result[SpecialGroup.LOGGED_IN] = loggedIn; const user1group = Group.create('user1group', 'user1group', false) as Group; user1group.members = Promise.resolve([user1]); result['user1group'] = user1group; const user2group = Group.create('user2group', 'user2group', false) as Group; user2group.members = Promise.resolve([user2]); result['user2group'] = user2group; const user1and2group = Group.create( 'user1and2group', 'user1and2group', false, ) as Group; user1and2group.members = Promise.resolve([user1, user2]); result['user1and2group'] = user1and2group; const user2and1group = Group.create( 'user2and1group', 'user2and1group', false, ) as Group; user2and1group.members = Promise.resolve([user2, user1]); result['user2and1group'] = user2and1group; return result; } /* * Create all GroupPermissions: For each group two GroupPermissions are created one with read permission and one with write permission. */ function createAllNoteGroupPermissions(): NoteGroupPermission[][] { const groups = createGroups(); /* * Helper function for creating GroupPermissions */ function createNoteGroupPermission( group: Group, write: boolean, ): NoteGroupPermission { return NoteGroupPermission.create(group, {} as Note, write); } const everybodyRead = createNoteGroupPermission( groups[SpecialGroup.EVERYONE], false, ); const everybodyWrite = createNoteGroupPermission( groups[SpecialGroup.EVERYONE], true, ); const loggedInRead = createNoteGroupPermission( groups[SpecialGroup.LOGGED_IN], false, ); const loggedInWrite = createNoteGroupPermission( groups[SpecialGroup.LOGGED_IN], true, ); const user1groupRead = createNoteGroupPermission( groups['user1group'], false, ); const user1groupWrite = createNoteGroupPermission( groups['user1group'], true, ); const user2groupRead = createNoteGroupPermission( groups['user2group'], false, ); const user2groupWrite = createNoteGroupPermission( groups['user2group'], true, ); const user1and2groupRead = createNoteGroupPermission( groups['user1and2group'], false, ); const user1and2groupWrite = createNoteGroupPermission( groups['user1and2group'], true, ); const user2and1groupRead = createNoteGroupPermission( groups['user2and1group'], false, ); const user2and1groupWrite = createNoteGroupPermission( groups['user2and1group'], true, ); return [ [user1groupRead, user1and2groupRead, user2and1groupRead, null], // group0: allow user1 to read via group [user2and1groupWrite, user1and2groupWrite, user1groupWrite, null], // group1: allow user1 to write via group [everybodyRead, everybodyWrite, null], // group2: permissions of the special group everybody [loggedInRead, loggedInWrite, null], // group3: permissions of the special group loggedIn [user2groupWrite, user2groupRead, null], // group4: don't allow user1 to read or write via group ]; } /* * creates the matrix multiplication of group0 to group4 of createAllNoteGroupPermissions */ function createNoteGroupPermissionsCombinations( guestPermission: GuestPermission, ): NoteGroupPermissionWithResultForUser[] { // for logged in users const noteGroupPermissions = createAllNoteGroupPermissions(); const result: NoteGroupPermissionWithResultForUser[] = []; for (const group0 of noteGroupPermissions[0]) { for (const group1 of noteGroupPermissions[1]) { for (const group2 of noteGroupPermissions[2]) { for (const group3 of noteGroupPermissions[3]) { for (const group4 of noteGroupPermissions[4]) { const insert: NoteGroupPermission[] = []; let readPermission = false; let writePermission = false; if (group0 !== null) { // user1 in ReadGroups readPermission = true; insert.push(group0); } if (group1 !== null) { // user1 in WriteGroups readPermission = true; writePermission = true; insert.push(group1); } if (group2 !== null) { // everybody group TODO config options switch (guestPermission) { case GuestPermission.CREATE_ALIAS: case GuestPermission.CREATE: case GuestPermission.WRITE: writePermission = writePermission || group2.canEdit; readPermission = true; break; case GuestPermission.READ: readPermission = true; } insert.push(group2); } if (group3 !== null) { // loggedIn users readPermission = true; writePermission = writePermission || group3.canEdit; insert.push(group3); } if (group4 !== null) { // user not in group insert.push(group4); } result.push({ permissions: insert, allowsRead: readPermission, allowsWrite: writePermission, }); } } } } } return result; } // inspired by https://stackoverflow.com/questions/9960908/permutations-in-javascript function permutator( inputArr: NoteGroupPermission[], ): NoteGroupPermission[][] { const results: NoteGroupPermission[][] = []; function permute( arr: NoteGroupPermission[], memo: NoteGroupPermission[], ): NoteGroupPermission[][] { let cur: NoteGroupPermission[]; for (let i = 0; i < arr.length; i++) { cur = arr.splice(i, 1); if (arr.length === 0) { results.push(memo.concat(cur)); } permute(arr.slice(), memo.concat(cur)); arr.splice(i, 0, cur[0]); } return results; } return permute(inputArr, []); } // takes each set of permissions from createNoteGroupPermissionsCombinations, permute them and add them to the list function permuteNoteGroupPermissions( noteGroupPermissions: NoteGroupPermissionWithResultForUser[], ): NoteGroupPermissionWithResultForUser[] { const result: NoteGroupPermissionWithResultForUser[] = []; for (const permission of noteGroupPermissions) { const permutations = permutator(permission.permissions); for (const permutation of permutations) { result.push({ permissions: permutation, allowsRead: permission.allowsRead, allowsWrite: permission.allowsWrite, }); } } return result; } describe('check if groups work with', () => { const guestPermission = GuestPermission.WRITE; const rawPermissions = createNoteGroupPermissionsCombinations(guestPermission); const permissions = permuteNoteGroupPermissions(rawPermissions); let i = 0; for (const permission of permissions) { const note = createNote(user2); note.groupPermissions = Promise.resolve(permission.permissions); let permissionString = ''; for (const perm of permission.permissions) { permissionString += ` ${perm.id}:${String(perm.canEdit)}`; } it(`mayWrite - test #${i}:${permissionString}`, async () => { service.guestPermission = guestPermission; expect(await service.mayWrite(user1, note)).toEqual( permission.allowsWrite, ); }); it(`mayRead - test #${i}:${permissionString}`, async () => { service.guestPermission = guestPermission; expect(await service.mayRead(user1, note)).toEqual( permission.allowsRead, ); }); i++; } }); describe('mayCreate works for', () => { it('logged in', () => { service.guestPermission = GuestPermission.DENY; expect(service.mayCreate(user1)).toBeTruthy(); }); it('guest denied', () => { service.guestPermission = GuestPermission.DENY; expect(service.mayCreate(null)).toBeFalsy(); }); it('guest read', () => { service.guestPermission = GuestPermission.READ; expect(service.mayCreate(null)).toBeFalsy(); }); it('guest write', () => { service.guestPermission = GuestPermission.WRITE; expect(service.mayCreate(null)).toBeFalsy(); }); it('guest create', () => { service.guestPermission = GuestPermission.CREATE; expect(service.mayCreate(null)).toBeTruthy(); }); it('guest create alias', () => { service.guestPermission = GuestPermission.CREATE_ALIAS; expect(service.mayCreate(null)).toBeTruthy(); }); }); describe('isOwner works', () => { it('for positive case', async () => { service.guestPermission = GuestPermission.DENY; expect(await service.isOwner(user1, notes[0])).toBeTruthy(); }); it('for negative case', async () => { service.guestPermission = GuestPermission.DENY; expect(await service.isOwner(user1, notes[1])).toBeFalsy(); }); }); describe('updateNotePermissions', () => { const userPermissionUpdate = new NoteUserPermissionUpdateDto(); userPermissionUpdate.username = 'hardcoded'; userPermissionUpdate.canEdit = true; const groupPermissionUpdate = new NoteGroupPermissionUpdateDto(); groupPermissionUpdate.groupName = 'testGroup'; groupPermissionUpdate.canEdit = false; const user = User.create(userPermissionUpdate.username, 'Testy') as User; const group = Group.create( groupPermissionUpdate.groupName, groupPermissionUpdate.groupName, false, ) as Group; const note = Note.create(user) as Note; describe('works', () => { it('with empty GroupPermissions and with empty UserPermissions', async () => { jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); const savedNote = await service.updateNotePermissions(note, { sharedToUsers: [], sharedToGroups: [], }); expect(await savedNote.userPermissions).toHaveLength(0); expect(await savedNote.groupPermissions).toHaveLength(0); }); it('with empty GroupPermissions and with new UserPermissions', async () => { jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); jest.spyOn(userRepo, 'findOne').mockResolvedValueOnce(user); const savedNote = await service.updateNotePermissions(note, { sharedToUsers: [userPermissionUpdate], sharedToGroups: [], }); expect(await savedNote.userPermissions).toHaveLength(1); expect( (await (await savedNote.userPermissions)[0].user).username, ).toEqual(userPermissionUpdate.username); expect((await savedNote.userPermissions)[0].canEdit).toEqual( userPermissionUpdate.canEdit, ); expect(await savedNote.groupPermissions).toHaveLength(0); }); it('with empty GroupPermissions and with existing UserPermissions', async () => { const noteWithPreexistingPermissions: Note = { ...note }; noteWithPreexistingPermissions.userPermissions = Promise.resolve([ { id: 1, note: Promise.resolve(noteWithPreexistingPermissions), user: Promise.resolve(user), canEdit: !userPermissionUpdate.canEdit, }, ]); jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); jest.spyOn(userRepo, 'findOne').mockResolvedValueOnce(user); const savedNote = await service.updateNotePermissions(note, { sharedToUsers: [userPermissionUpdate], sharedToGroups: [], }); expect(await savedNote.userPermissions).toHaveLength(1); expect( (await (await savedNote.userPermissions)[0].user).username, ).toEqual(userPermissionUpdate.username); expect((await savedNote.userPermissions)[0].canEdit).toEqual( userPermissionUpdate.canEdit, ); expect(await savedNote.groupPermissions).toHaveLength(0); }); it('with new GroupPermissions and with empty UserPermissions', async () => { jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); jest.spyOn(groupRepo, 'findOne').mockResolvedValueOnce(group); const savedNote = await service.updateNotePermissions(note, { sharedToUsers: [], sharedToGroups: [groupPermissionUpdate], }); expect(await savedNote.userPermissions).toHaveLength(0); expect( (await (await savedNote.groupPermissions)[0].group).name, ).toEqual(groupPermissionUpdate.groupName); expect((await savedNote.groupPermissions)[0].canEdit).toEqual( groupPermissionUpdate.canEdit, ); }); it('with new GroupPermissions and with new UserPermissions', async () => { jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); jest.spyOn(userRepo, 'findOne').mockResolvedValueOnce(user); jest.spyOn(groupRepo, 'findOne').mockResolvedValueOnce(group); const savedNote = await service.updateNotePermissions(note, { sharedToUsers: [userPermissionUpdate], sharedToGroups: [groupPermissionUpdate], }); expect( (await (await savedNote.userPermissions)[0].user).username, ).toEqual(userPermissionUpdate.username); expect((await savedNote.userPermissions)[0].canEdit).toEqual( userPermissionUpdate.canEdit, ); expect( (await (await savedNote.groupPermissions)[0].group).name, ).toEqual(groupPermissionUpdate.groupName); expect((await savedNote.groupPermissions)[0].canEdit).toEqual( groupPermissionUpdate.canEdit, ); }); it('with new GroupPermissions and with existing UserPermissions', async () => { const noteWithUserPermission: Note = { ...note }; noteWithUserPermission.userPermissions = Promise.resolve([ { id: 1, note: Promise.resolve(noteWithUserPermission), user: Promise.resolve(user), canEdit: !userPermissionUpdate.canEdit, }, ]); jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); jest.spyOn(userRepo, 'findOne').mockResolvedValueOnce(user); jest.spyOn(groupRepo, 'findOne').mockResolvedValueOnce(group); const savedNote = await service.updateNotePermissions( noteWithUserPermission, { sharedToUsers: [userPermissionUpdate], sharedToGroups: [groupPermissionUpdate], }, ); expect( (await (await savedNote.userPermissions)[0].user).username, ).toEqual(userPermissionUpdate.username); expect((await savedNote.userPermissions)[0].canEdit).toEqual( userPermissionUpdate.canEdit, ); expect( (await (await savedNote.groupPermissions)[0].group).name, ).toEqual(groupPermissionUpdate.groupName); expect((await savedNote.groupPermissions)[0].canEdit).toEqual( groupPermissionUpdate.canEdit, ); }); it('with existing GroupPermissions and with empty UserPermissions', async () => { const noteWithPreexistingPermissions: Note = { ...note }; noteWithPreexistingPermissions.groupPermissions = Promise.resolve([ { id: 1, note: Promise.resolve(noteWithPreexistingPermissions), group: Promise.resolve(group), canEdit: !groupPermissionUpdate.canEdit, }, ]); jest.spyOn(groupRepo, 'findOne').mockResolvedValueOnce(group); jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); const savedNote = await service.updateNotePermissions( noteWithPreexistingPermissions, { sharedToUsers: [], sharedToGroups: [groupPermissionUpdate], }, ); expect(await savedNote.userPermissions).toHaveLength(0); expect( (await (await savedNote.groupPermissions)[0].group).name, ).toEqual(groupPermissionUpdate.groupName); expect((await savedNote.groupPermissions)[0].canEdit).toEqual( groupPermissionUpdate.canEdit, ); }); it('with existing GroupPermissions and with new UserPermissions', async () => { const noteWithPreexistingPermissions: Note = { ...note }; noteWithPreexistingPermissions.groupPermissions = Promise.resolve([ { id: 1, note: Promise.resolve(noteWithPreexistingPermissions), group: Promise.resolve(group), canEdit: !groupPermissionUpdate.canEdit, }, ]); jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); jest.spyOn(userRepo, 'findOne').mockResolvedValueOnce(user); jest.spyOn(groupRepo, 'findOne').mockResolvedValueOnce(group); const savedNote = await service.updateNotePermissions( noteWithPreexistingPermissions, { sharedToUsers: [userPermissionUpdate], sharedToGroups: [groupPermissionUpdate], }, ); expect( (await (await savedNote.userPermissions)[0].user).username, ).toEqual(userPermissionUpdate.username); expect((await savedNote.userPermissions)[0].canEdit).toEqual( userPermissionUpdate.canEdit, ); expect( (await (await savedNote.groupPermissions)[0].group).name, ).toEqual(groupPermissionUpdate.groupName); expect((await savedNote.groupPermissions)[0].canEdit).toEqual( groupPermissionUpdate.canEdit, ); }); it('with existing GroupPermissions and with existing UserPermissions', async () => { const noteWithPreexistingPermissions: Note = { ...note }; noteWithPreexistingPermissions.groupPermissions = Promise.resolve([ { id: 1, note: Promise.resolve(noteWithPreexistingPermissions), group: Promise.resolve(group), canEdit: !groupPermissionUpdate.canEdit, }, ]); noteWithPreexistingPermissions.userPermissions = Promise.resolve([ { id: 1, note: Promise.resolve(noteWithPreexistingPermissions), user: Promise.resolve(user), canEdit: !userPermissionUpdate.canEdit, }, ]); jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); jest.spyOn(userRepo, 'findOne').mockResolvedValueOnce(user); jest.spyOn(groupRepo, 'findOne').mockResolvedValueOnce(group); const savedNote = await service.updateNotePermissions( noteWithPreexistingPermissions, { sharedToUsers: [userPermissionUpdate], sharedToGroups: [groupPermissionUpdate], }, ); expect( (await (await savedNote.userPermissions)[0].user).username, ).toEqual(userPermissionUpdate.username); expect((await savedNote.userPermissions)[0].canEdit).toEqual( userPermissionUpdate.canEdit, ); expect( (await (await savedNote.groupPermissions)[0].group).name, ).toEqual(groupPermissionUpdate.groupName); expect((await savedNote.groupPermissions)[0].canEdit).toEqual( groupPermissionUpdate.canEdit, ); }); }); describe('fails:', () => { it('userPermissions has duplicate entries', async () => { await expect( service.updateNotePermissions(note, { sharedToUsers: [userPermissionUpdate, userPermissionUpdate], sharedToGroups: [], }), ).rejects.toThrow(PermissionsUpdateInconsistentError); }); it('groupPermissions has duplicate entries', async () => { await expect( service.updateNotePermissions(note, { sharedToUsers: [], sharedToGroups: [groupPermissionUpdate, groupPermissionUpdate], }), ).rejects.toThrow(PermissionsUpdateInconsistentError); }); it('userPermissions and groupPermissions have duplicate entries', async () => { await expect( service.updateNotePermissions(note, { sharedToUsers: [userPermissionUpdate, userPermissionUpdate], sharedToGroups: [groupPermissionUpdate, groupPermissionUpdate], }), ).rejects.toThrow(PermissionsUpdateInconsistentError); }); }); }); describe('setUserPermission', () => { describe('works', () => { it('with user not added before and editable', async () => { jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); const note = Note.create(null) as Note; const user = User.create('test', 'Testy') as User; const resultNote = await service.setUserPermission(note, user, true); const noteUserPermission = NoteUserPermission.create(user, note, true); expect((await resultNote.userPermissions)[0]).toStrictEqual( noteUserPermission, ); }); it('with user not added before and not editable', async () => { jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); const note = Note.create(null) as Note; const user = User.create('test', 'Testy') as User; const resultNote = await service.setUserPermission(note, user, false); const noteUserPermission = NoteUserPermission.create(user, note, false); expect((await resultNote.userPermissions)[0]).toStrictEqual( noteUserPermission, ); }); it('with user added before and editable', async () => { jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); const note = Note.create(null) as Note; const user = User.create('test', 'Testy') as User; note.userPermissions = Promise.resolve([ NoteUserPermission.create(user, note, false), ]); const resultNote = await service.setUserPermission(note, user, true); const noteUserPermission = NoteUserPermission.create(user, note, true); expect((await resultNote.userPermissions)[0]).toStrictEqual( noteUserPermission, ); }); it('with user added before and not editable', async () => { jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); const note = Note.create(null) as Note; const user = User.create('test', 'Testy') as User; note.userPermissions = Promise.resolve([ NoteUserPermission.create(user, note, true), ]); const resultNote = await service.setUserPermission(note, user, false); const noteUserPermission = NoteUserPermission.create(user, note, false); expect((await resultNote.userPermissions)[0]).toStrictEqual( noteUserPermission, ); }); }); }); describe('removeUserPermission', () => { describe('works', () => { it('with user added before and editable', async () => { jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); const note = Note.create(null) as Note; const user = User.create('test', 'Testy') as User; note.userPermissions = Promise.resolve([ NoteUserPermission.create(user, note, true), ]); const resultNote = await service.removeUserPermission(note, user); expect((await resultNote.userPermissions).length).toStrictEqual(0); }); it('with user not added before and not editable', async () => { jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); const note = Note.create(null) as Note; const user = User.create('test', 'Testy') as User; note.userPermissions = Promise.resolve([ NoteUserPermission.create(user, note, false), ]); const resultNote = await service.removeUserPermission(note, user); expect((await resultNote.userPermissions).length).toStrictEqual(0); }); }); }); describe('setGroupPermission', () => { describe('works', () => { it('with group not added before and editable', async () => { jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); const note = Note.create(null) as Note; const group = Group.create('test', 'Testy', false) as Group; const resultNote = await service.setGroupPermission(note, group, true); const noteGroupPermission = NoteGroupPermission.create( group, note, true, ); expect((await resultNote.groupPermissions)[0]).toStrictEqual( noteGroupPermission, ); }); it('with group not added before and not editable', async () => { jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); const note = Note.create(null) as Note; const group = Group.create('test', 'Testy', false) as Group; const resultNote = await service.setGroupPermission(note, group, false); const noteGroupPermission = NoteGroupPermission.create( group, note, false, ); expect((await resultNote.groupPermissions)[0]).toStrictEqual( noteGroupPermission, ); }); it('with group added before and editable', async () => { jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); const note = Note.create(null) as Note; const group = Group.create('test', 'Testy', false) as Group; note.groupPermissions = Promise.resolve([ NoteGroupPermission.create(group, note, false), ]); const resultNote = await service.setGroupPermission(note, group, true); const noteGroupPermission = NoteGroupPermission.create( group, note, true, ); expect((await resultNote.groupPermissions)[0]).toStrictEqual( noteGroupPermission, ); }); it('with group added before and not editable', async () => { jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); const note = Note.create(null) as Note; const group = Group.create('test', 'Testy', false) as Group; note.groupPermissions = Promise.resolve([ NoteGroupPermission.create(group, note, true), ]); const resultNote = await service.setGroupPermission(note, group, false); const noteGroupPermission = NoteGroupPermission.create( group, note, false, ); expect((await resultNote.groupPermissions)[0]).toStrictEqual( noteGroupPermission, ); }); }); }); describe('removeGroupPermission', () => { describe('works', () => { it('with user added before and editable', async () => { jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); const note = Note.create(null) as Note; const group = Group.create('test', 'Testy', false) as Group; note.groupPermissions = Promise.resolve([ NoteGroupPermission.create(group, note, true), ]); const resultNote = await service.removeGroupPermission(note, group); expect((await resultNote.groupPermissions).length).toStrictEqual(0); }); it('with user not added before and not editable', async () => { jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); const note = Note.create(null) as Note; const group = Group.create('test', 'Testy', false) as Group; note.groupPermissions = Promise.resolve([ NoteGroupPermission.create(group, note, false), ]); const resultNote = await service.removeGroupPermission(note, group); expect((await resultNote.groupPermissions).length).toStrictEqual(0); }); }); }); describe('changeOwner', () => { it('works', async () => { const note = Note.create(null) as Note; const user = User.create('test', 'Testy') as User; jest .spyOn(noteRepo, 'save') // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore .mockImplementationOnce(async (entry: Note) => { return entry; }); const resultNote = await service.changeOwner(note, user); expect(await resultNote.owner).toStrictEqual(user); }); }); });