hedgedoc/backend/src/permissions/permissions.service.spec.ts
Tilman Vatteroth bf30cbcf48 fix(repository): Move backend code into subdirectory
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
2022-10-30 22:46:42 +01:00

1411 lines
51 KiB
TypeScript

/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ConfigModule } from '@nestjs/config';
import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter';
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 { DefaultAccessPermission } from '../config/default-access-permission.enum';
import { GuestAccess } from '../config/guest_access.enum';
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 {
createDefaultMockNoteConfig,
registerNoteConfig,
} from '../config/mock/note.config.mock';
import { NoteConfig } from '../config/note.config';
import { PermissionsUpdateInconsistentError } from '../errors/errors';
import { eventModuleConfig, NoteEvent } from '../events';
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 { PermissionsService } from './permissions.service';
describe('PermissionsService', () => {
let service: PermissionsService;
let notes: Note[];
let noteRepo: Repository<Note>;
let userRepo: Repository<User>;
let groupRepo: Repository<Group>;
let eventEmitter: EventEmitter2;
const noteMockConfig: NoteConfig = createDefaultMockNoteConfig();
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<User>(
'',
new EntityManager(
new DataSource({
type: 'sqlite',
database: ':memory:',
}),
),
undefined,
);
noteRepo = new Repository<Note>(
'',
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,
registerNoteConfig(noteMockConfig),
],
}),
GroupsModule,
EventEmitterModule.forRoot(eventModuleConfig),
],
})
.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>(PermissionsService);
notes = await createNoteUserPermissionNotes();
groupRepo = module.get<Repository<Group>>(getRepositoryToken(Group));
noteRepo = module.get<Repository<Note>>(getRepositoryToken(Note));
eventEmitter = module.get<EventEmitter2>(EventEmitter2);
});
afterEach(() => {
jest.clearAllMocks();
});
// 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<Note[]> {
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 noteEverybodyNone = createNote(user1);
noteEverybodyNone.groupPermissions = Promise.resolve([]);
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,
noteEverybodyNone,
];
}
describe('mayRead works with', () => {
it('Owner', async () => {
expect(await service.mayRead(user1, notes[0])).toBeTruthy();
expect(await service.mayRead(user1, notes[7])).toBeFalsy();
});
it('userPermission read', async () => {
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 () => {
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', () => {
beforeEach(() => {
noteMockConfig.permissions.default.loggedIn =
DefaultAccessPermission.WRITE;
noteMockConfig.permissions.default.everyone =
DefaultAccessPermission.WRITE;
});
describe('with guest access deny', () => {
beforeEach(() => {
noteMockConfig.guestAccess = GuestAccess.DENY;
});
it('guest permission none', async () => {
expect(await service.mayRead(null, notes[10])).toBeFalsy();
});
it('guest permission read', async () => {
expect(await service.mayRead(null, notes[8])).toBeFalsy();
});
it('guest permission write', async () => {
expect(await service.mayRead(null, notes[9])).toBeFalsy();
});
});
describe('with guest access read', () => {
beforeEach(() => {
noteMockConfig.guestAccess = GuestAccess.READ;
});
it('guest permission none', async () => {
expect(await service.mayRead(null, notes[10])).toBeFalsy();
});
it('guest permission read', async () => {
expect(await service.mayRead(null, notes[8])).toBeTruthy();
});
it('guest permission write', async () => {
expect(await service.mayRead(null, notes[9])).toBeTruthy();
});
});
describe('with guest access write', () => {
beforeEach(() => {
noteMockConfig.guestAccess = GuestAccess.WRITE;
});
it('guest permission none', async () => {
expect(await service.mayRead(null, notes[10])).toBeFalsy();
});
it('guest permission read', async () => {
expect(await service.mayRead(null, notes[8])).toBeTruthy();
});
it('guest permission write', async () => {
expect(await service.mayRead(null, notes[9])).toBeTruthy();
});
});
describe('with guest access create', () => {
beforeEach(() => {
noteMockConfig.guestAccess = GuestAccess.CREATE;
});
it('guest permission none', async () => {
expect(await service.mayRead(null, notes[10])).toBeFalsy();
});
it('guest permission read', async () => {
expect(await service.mayRead(null, notes[8])).toBeTruthy();
});
it('guest permission write', async () => {
expect(await service.mayRead(null, notes[9])).toBeTruthy();
});
});
});
});
describe('mayWrite works with', () => {
it('Owner', async () => {
expect(await service.mayWrite(user1, notes[0])).toBeTruthy();
expect(await service.mayWrite(user1, notes[7])).toBeFalsy();
});
it('userPermission read', async () => {
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 () => {
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', () => {
beforeEach(() => {
noteMockConfig.permissions.default.loggedIn =
DefaultAccessPermission.WRITE;
noteMockConfig.permissions.default.everyone =
DefaultAccessPermission.WRITE;
});
describe('with guest access deny', () => {
beforeEach(() => {
noteMockConfig.guestAccess = GuestAccess.DENY;
});
it('guest permission none', async () => {
expect(await service.mayWrite(null, notes[10])).toBeFalsy();
});
it('guest permission read', async () => {
expect(await service.mayWrite(null, notes[8])).toBeFalsy();
});
it('guest permission write', async () => {
expect(await service.mayWrite(null, notes[9])).toBeFalsy();
});
});
describe('with guest access read', () => {
beforeEach(() => {
noteMockConfig.guestAccess = GuestAccess.READ;
});
it('guest permission none', async () => {
expect(await service.mayWrite(null, notes[10])).toBeFalsy();
});
it('guest permission read', async () => {
expect(await service.mayWrite(null, notes[8])).toBeFalsy();
});
it('guest permission write', async () => {
expect(await service.mayWrite(null, notes[9])).toBeFalsy();
});
});
describe('with guest access write', () => {
beforeEach(() => {
noteMockConfig.guestAccess = GuestAccess.WRITE;
});
it('guest permission none', async () => {
expect(await service.mayWrite(null, notes[10])).toBeFalsy();
});
it('guest permission read', async () => {
expect(await service.mayWrite(null, notes[8])).toBeFalsy();
});
it('guest permission write', async () => {
expect(await service.mayWrite(null, notes[9])).toBeTruthy();
});
});
describe('with guest access create', () => {
beforeEach(() => {
noteMockConfig.guestAccess = GuestAccess.CREATE;
});
it('guest permission none', async () => {
expect(await service.mayWrite(null, notes[10])).toBeFalsy();
});
it('guest permission read', async () => {
expect(await service.mayWrite(null, notes[8])).toBeFalsy();
});
it('guest permission write', async () => {
expect(await service.mayWrite(null, notes[9])).toBeTruthy();
});
});
});
});
/*
* 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 } = {};
result[SpecialGroup.EVERYONE] = Group.create(
SpecialGroup.EVERYONE,
SpecialGroup.EVERYONE,
true,
) as Group;
result[SpecialGroup.LOGGED_IN] = Group.create(
SpecialGroup.LOGGED_IN,
SpecialGroup.LOGGED_IN,
true,
) as Group;
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 | null)[][] {
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(
everyoneDefaultPermission: DefaultAccessPermission,
): 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) {
if (
everyoneDefaultPermission === DefaultAccessPermission.WRITE
) {
writePermission = writePermission || group2.canEdit;
readPermission = true;
} else if (
everyoneDefaultPermission === DefaultAccessPermission.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 rawPermissions = createNoteGroupPermissionsCombinations(
DefaultAccessPermission.WRITE,
);
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 () => {
expect(await service.mayWrite(user1, note)).toEqual(
permission.allowsWrite,
);
});
it(`mayRead - test #${i}:${permissionString}`, async () => {
expect(await service.mayRead(user1, note)).toEqual(
permission.allowsRead,
);
});
i++;
}
});
describe('mayCreate', () => {
it('allows creation for logged in', () => {
expect(service.mayCreate(user1)).toBeTruthy();
});
it('allows creation of notes for guests with permission', () => {
noteMockConfig.guestAccess = GuestAccess.CREATE;
noteMockConfig.permissions.default.loggedIn =
DefaultAccessPermission.WRITE;
noteMockConfig.permissions.default.everyone =
DefaultAccessPermission.WRITE;
expect(service.mayCreate(null)).toBeTruthy();
});
it('denies creation of notes for guests without permission', () => {
noteMockConfig.guestAccess = GuestAccess.WRITE;
noteMockConfig.permissions.default.loggedIn =
DefaultAccessPermission.WRITE;
noteMockConfig.permissions.default.everyone =
DefaultAccessPermission.WRITE;
expect(service.mayCreate(null)).toBeFalsy();
});
});
describe('isOwner works', () => {
it('for positive case', async () => {
expect(await service.isOwner(user1, notes[0])).toBeTruthy();
});
it('for negative case', async () => {
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;
it('emits PERMISSION_CHANGE event', async () => {
jest
.spyOn(noteRepo, 'save')
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
.mockImplementationOnce(async (entry: Note) => {
return entry;
});
const mockedEventEmitter = jest
.spyOn(eventEmitter, 'emit')
.mockImplementationOnce((event) => {
expect(event).toEqual(NoteEvent.PERMISSION_CHANGE);
return true;
});
expect(mockedEventEmitter).not.toHaveBeenCalled();
await service.updateNotePermissions(note, {
sharedToUsers: [],
sharedToGroups: [],
});
expect(mockedEventEmitter).toHaveBeenCalled();
});
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', () => {
it('emits PERMISSION_CHANGE event', 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 mockedEventEmitter = jest
.spyOn(eventEmitter, 'emit')
.mockImplementationOnce((event) => {
expect(event).toEqual(NoteEvent.PERMISSION_CHANGE);
return true;
});
expect(mockedEventEmitter).not.toHaveBeenCalled();
await service.setUserPermission(note, user, true);
expect(mockedEventEmitter).toHaveBeenCalled();
});
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', () => {
it('emits PERMISSION_CHANGE event', 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 mockedEventEmitter = jest
.spyOn(eventEmitter, 'emit')
.mockImplementationOnce((event) => {
expect(event).toEqual(NoteEvent.PERMISSION_CHANGE);
return true;
});
expect(mockedEventEmitter).not.toHaveBeenCalled();
await service.removeUserPermission(note, user);
expect(mockedEventEmitter).toHaveBeenCalled();
});
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', () => {
it('emits PERMISSION_CHANGE event', 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 mockedEventEmitter = jest
.spyOn(eventEmitter, 'emit')
.mockImplementationOnce((event) => {
expect(event).toEqual(NoteEvent.PERMISSION_CHANGE);
return true;
});
expect(mockedEventEmitter).not.toHaveBeenCalled();
await service.setGroupPermission(note, group, true);
expect(mockedEventEmitter).toHaveBeenCalled();
});
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', () => {
it('emits PERMISSION_CHANGE event', 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 mockedEventEmitter = jest
.spyOn(eventEmitter, 'emit')
.mockImplementationOnce((event) => {
expect(event).toEqual(NoteEvent.PERMISSION_CHANGE);
return true;
});
expect(mockedEventEmitter).not.toHaveBeenCalled();
await service.removeGroupPermission(note, group);
expect(mockedEventEmitter).toHaveBeenCalled();
});
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);
});
});
});