Skip to content

Commit

Permalink
SPSH-816-permissions-rollen (#564)
Browse files Browse the repository at this point in the history
* findRollen only with permissions

* fixed failing test

* more tests

* fixed array check

* check for Rollen

* SPSH-816: PR Review

---------

Co-authored-by: Phael Mouko <[email protected]>
  • Loading branch information
YoussefBouch and phaelcg authored Jul 1, 2024
1 parent 3ecbfec commit a36d16c
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 14 deletions.
41 changes: 39 additions & 2 deletions src/modules/rolle/api/rolle.controller.integration-spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { faker } from '@faker-js/faker';
import { EntityManager, MikroORM } from '@mikro-orm/core';
import { INestApplication } from '@nestjs/common';
import { APP_PIPE } from '@nestjs/core';
import { CallHandler, ExecutionContext, INestApplication } from '@nestjs/common';
import { APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core';
import { Test, TestingModule } from '@nestjs/testing';
import request, { Response } from 'supertest';
import { App } from 'supertest/types.js';
Expand Down Expand Up @@ -30,13 +30,19 @@ import { RolleWithServiceProvidersResponse } from './rolle-with-serviceprovider.
import { OrganisationRepository } from '../../organisation/persistence/organisation.repository.js';
import { PagedResponse } from '../../../shared/paging/index.js';
import { ServiceProviderIdNameResponse } from './serviceprovider-id-name.response.js';
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { Observable } from 'rxjs';
import { PersonPermissionsRepo } from '../../authentication/domain/person-permission.repo.js';
import { PassportUser } from '../../authentication/types/user.js';
import { Request } from 'express';

describe('Rolle API', () => {
let app: INestApplication;
let orm: MikroORM;
let em: EntityManager;
let rolleRepo: RolleRepo;
let serviceProviderRepo: ServiceProviderRepo;
let personpermissionsRepoMock: DeepMocked<PersonPermissionsRepo>;

beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
Expand All @@ -51,6 +57,24 @@ describe('Rolle API', () => {
provide: APP_PIPE,
useClass: GlobalValidationPipe,
},
{
provide: APP_INTERCEPTOR,
useValue: {
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
const req: Request = context.switchToHttp().getRequest();
req.passportUser = createMock<PassportUser>({
async personPermissions() {
return personpermissionsRepoMock.loadPersonPermissions('');
},
});
return next.handle();
},
},
},
{
provide: PersonPermissionsRepo,
useValue: createMock<PersonPermissionsRepo>(),
},
OrganisationRepository,
RolleFactory,
ServiceProviderRepo,
Expand All @@ -61,6 +85,7 @@ describe('Rolle API', () => {
em = module.get(EntityManager);
rolleRepo = module.get(RolleRepo);
serviceProviderRepo = module.get(ServiceProviderRepo);
personpermissionsRepoMock = module.get(PersonPermissionsRepo);
await DatabaseTestModule.setupDatabase(module.get(MikroORM));
app = module.createNestApplication();
await app.init();
Expand Down Expand Up @@ -211,6 +236,18 @@ describe('Rolle API', () => {
expect(pagedResponse.items).toHaveLength(3);
});

it('should return no rollen', async () => {
const response: Response = await request(app.getHttpServer() as App)
.get('/rolle')
.send();

expect(response.status).toBe(200);
expect(response.body).toBeInstanceOf(Object);
const pagedResponse: PagedResponse<RolleWithServiceProvidersResponse> =
response.body as PagedResponse<RolleWithServiceProvidersResponse>;
expect(pagedResponse.items).toHaveLength(0);
});

it('should return rollen with the given queried name', async () => {
const testRolle: { name: string } = await rolleRepo.save(DoFactory.createRolle(false));

Expand Down
6 changes: 5 additions & 1 deletion src/modules/rolle/api/rolle.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { FindRolleByIdParams } from './find-rolle-by-id.params.js';
import { OrganisationService } from '../../organisation/domain/organisation.service.js';
import { OrganisationRepository } from '../../organisation/persistence/organisation.repository.js';
import { RolleNameQueryParams } from './rolle-name-query.param.js';
import { PersonPermissions } from '../../authentication/domain/person-permissions.js';

describe('Rolle API with mocked ServiceProviderRepo', () => {
let rolleRepoMock: DeepMocked<RolleRepo>;
Expand Down Expand Up @@ -84,11 +85,14 @@ describe('Rolle API with mocked ServiceProviderRepo', () => {
const params: RolleNameQueryParams = {
searchStr: faker.string.alpha(),
};
const permissions: DeepMocked<PersonPermissions> = createMock<PersonPermissions>();
permissions.getOrgIdsWithSystemrecht.mockResolvedValueOnce([]);

//mock getRollenByName
rolleRepoMock.findByName.mockResolvedValueOnce(undefined);
//mock call to get sp (direct in controller-method)
serviceProviderRepoMock.findById.mockResolvedValueOnce(undefined);
await expect(rolleController.findRollen(params)).resolves.not.toThrow(Error);
await expect(rolleController.findRollen(params, permissions)).resolves.not.toThrow(Error);
});
});
});
Expand Down
21 changes: 11 additions & 10 deletions src/modules/rolle/api/rolle.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ import { ServiceProviderResponse } from '../../service-provider/api/service-prov
import { SchulConnexError } from '../../../shared/error/schul-connex.error.js';
import { RolleExceptionFilter } from './rolle-exception-filter.js';
import { Paged, PagedResponse, PagingHeadersObject } from '../../../shared/paging/index.js';
import { Permissions } from '../../authentication/api/permissions.decorator.js';
import { PersonPermissions } from '../../authentication/domain/person-permissions.js';

@UseFilters(new SchulConnexValidationErrorFilter(), new RolleExceptionFilter())
@ApiTags('rolle')
Expand All @@ -75,18 +77,16 @@ export class RolleController {
@ApiInternalServerErrorResponse({ description: 'Internal server error while getting all rollen.' })
public async findRollen(
@Query() queryParams: RolleNameQueryParams,
@Permissions() permissions: PersonPermissions,
): Promise<PagedResponse<RolleWithServiceProvidersResponse>> {
let rollen: Option<Rolle<true>[]>;

if (queryParams.searchStr) {
rollen = await this.rolleRepo.findByName(queryParams.searchStr, queryParams.limit, queryParams.offset);
} else {
rollen = await this.rolleRepo.find(queryParams.limit, queryParams.offset);
}

const serviceProviders: ServiceProvider<true>[] = await this.serviceProviderRepo.find();
const rollen: Option<Rolle<true>[]> = await this.rolleRepo.findRollenAuthorized(
permissions,
queryParams.searchStr,
queryParams.limit,
queryParams.offset,
);

if (!rollen) {
if (!rollen || rollen.length === 0) {
const pagedRolleWithServiceProvidersResponse: Paged<RolleWithServiceProvidersResponse> = {
total: 0,
offset: 0,
Expand All @@ -95,6 +95,7 @@ export class RolleController {
};
return new PagedResponse(pagedRolleWithServiceProvidersResponse);
}
const serviceProviders: ServiceProvider<true>[] = await this.serviceProviderRepo.find();

const rollenWithServiceProvidersResponses: RolleWithServiceProvidersResponse[] = rollen.map(
(r: Rolle<true>) => {
Expand Down
70 changes: 70 additions & 0 deletions src/modules/rolle/repo/rolle.repo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import { ServiceProviderRepo } from '../../service-provider/repo/service-provide
import { ServiceProvider } from '../../service-provider/domain/service-provider.js';
import { EventService } from '../../../core/eventbus/index.js';
import { OrganisationRepository } from '../../organisation/persistence/organisation.repository.js';
import { PersonPermissions } from '../../authentication/domain/person-permissions.js';
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { OrganisationID } from '../../../shared/types/index.js';

describe('RolleRepo', () => {
let module: TestingModule;
Expand Down Expand Up @@ -118,7 +121,74 @@ describe('RolleRepo', () => {
expect(rolle).toBeNull();
});
});
describe('findRollenAuthorized', () => {
it('should return no rollen because there are none', async () => {
const organisationId: OrganisationID = faker.string.uuid();

const permissions: DeepMocked<PersonPermissions> = createMock<PersonPermissions>();
permissions.getOrgIdsWithSystemrecht.mockResolvedValueOnce([organisationId]);

const rolleResult: Option<Rolle<true>[]> = await sut.findRollenAuthorized(permissions, undefined, 10, 0);

expect(rolleResult?.length).toBe(0);
});

it('should return the rollen when permissions are sufficient', async () => {
const organisationId: OrganisationID = faker.string.uuid();
await sut.save(DoFactory.createRolle(false, { administeredBySchulstrukturknoten: organisationId }));

const permissions: DeepMocked<PersonPermissions> = createMock<PersonPermissions>();
permissions.getOrgIdsWithSystemrecht.mockResolvedValueOnce([organisationId]);

const rolleResult: Option<Rolle<true>[]> = await sut.findRollenAuthorized(permissions, undefined, 10, 0);

expect(rolleResult?.length).toBe(1);
});

it('should return empty array when permissions are insufficient', async () => {
const organisationId: OrganisationID = faker.string.uuid();
await sut.save(DoFactory.createRolle(false, { administeredBySchulstrukturknoten: organisationId }));

const permissions: DeepMocked<PersonPermissions> = createMock<PersonPermissions>();
permissions.getOrgIdsWithSystemrecht.mockResolvedValueOnce([]);

const rolleResult: Option<Rolle<true>[]> = await sut.findRollenAuthorized(permissions, undefined, 10, 0);

expect(rolleResult?.length).toBe(0);
});

it('should filter rollen based on search string and permissions', async () => {
const organisationId: OrganisationID = faker.string.uuid();
await sut.save(
DoFactory.createRolle(false, { administeredBySchulstrukturknoten: organisationId, name: 'Test' }),
);
await sut.save(
DoFactory.createRolle(false, {
administeredBySchulstrukturknoten: organisationId,
name: 'AnotherName',
}),
);

const permissions: DeepMocked<PersonPermissions> = createMock<PersonPermissions>();
permissions.getOrgIdsWithSystemrecht.mockResolvedValueOnce([organisationId]);

const rolleResult: Option<Rolle<true>[]> = await sut.findRollenAuthorized(permissions, 'Test', 10, 0);

expect(rolleResult?.length).toBe(1);
});

it('should return all rollen when no search string is provided and permissions are sufficient', async () => {
const organisationId: OrganisationID = faker.string.uuid();
await sut.save(DoFactory.createRolle(false, { administeredBySchulstrukturknoten: organisationId }));

const permissions: DeepMocked<PersonPermissions> = createMock<PersonPermissions>();
permissions.getOrgIdsWithSystemrecht.mockResolvedValueOnce([organisationId]);

const rolleResult: Option<Rolle<true>[]> = await sut.findRollenAuthorized(permissions, undefined, 10, 0);

expect(rolleResult?.length).toBe(1);
});
});
describe('findByName', () => {
it('should return the rolle', async () => {
const rolle: Rolle<true> = await sut.save(DoFactory.createRolle(false));
Expand Down
39 changes: 38 additions & 1 deletion src/modules/rolle/repo/rolle.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { RolleMerkmalEntity } from '../entity/rolle-merkmal.entity.js';
import { RolleEntity } from '../entity/rolle.entity.js';
import { RolleFactory } from '../domain/rolle.factory.js';
import { RolleServiceProviderEntity } from '../entity/rolle-service-provider.entity.js';
import { RolleID } from '../../../shared/types/index.js';
import { OrganisationID, RolleID } from '../../../shared/types/index.js';
import { RolleSystemrechtEntity } from '../entity/rolle-systemrecht.entity.js';
import { PersonPermissions } from '../../authentication/domain/person-permissions.js';

/**
* @deprecated Not for use outside of rolle-repo, export will be removed at a later date
Expand Down Expand Up @@ -129,6 +130,42 @@ export class RolleRepo {
return rollen.map((rolle: RolleEntity) => mapEntityToAggregate(rolle, this.rolleFactory));
}

public async findRollenAuthorized(
permissions: PersonPermissions,
searchStr?: string,
limit?: number,
offset?: number,
): Promise<Option<Rolle<true>[]>> {
let rollen: Option<RolleEntity[]>;
if (searchStr) {
rollen = await this.em.find(
this.entityName,
{ name: { $ilike: '%' + searchStr + '%' } },
{ populate: ['merkmale', 'systemrechte', 'serviceProvider'] as const, limit: limit, offset: offset },
);
} else {
rollen = await this.em.findAll(this.entityName, {
populate: ['merkmale', 'systemrechte', 'serviceProvider'] as const,
limit: limit,
offset: offset,
});
}
if (rollen.length === 0) {
return [];
}

const orgIdsWithRecht: OrganisationID[] = await permissions.getOrgIdsWithSystemrecht(
[RollenSystemRecht.ROLLEN_VERWALTEN],
true,
);

const filteredRollen: RolleEntity[] = rollen.filter((rolle: RolleEntity) =>
orgIdsWithRecht.includes(rolle.administeredBySchulstrukturknoten),
);

return filteredRollen.map((rolle: RolleEntity) => mapEntityToAggregate(rolle, this.rolleFactory));
}

public async exists(id: RolleID): Promise<boolean> {
const rolle: Option<Loaded<RolleEntity, never, 'id', never>> = await this.em.findOne(
RolleEntity,
Expand Down

0 comments on commit a36d16c

Please sign in to comment.