Skip to content

Commit

Permalink
Merge branch 'main' into SPSH-1652
Browse files Browse the repository at this point in the history
  • Loading branch information
pkleybolte authored Jan 10, 2025
2 parents d5a4eb6 + efbd396 commit b4562b5
Show file tree
Hide file tree
Showing 21 changed files with 2,011 additions and 1,241 deletions.
2,238 changes: 1,241 additions & 997 deletions migrations/.snapshot-dbildungs-iam-server.json

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions migrations/Migration20241219124504-S.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Migration } from '@mikro-orm/migrations';

export class Migration20241219124504 extends Migration {
public async up(): Promise<void> {

this.addSql('alter table "importvorgang" rename column "import_by_person_id" to "person_id";');

this.addSql(
'alter table "importvorgang" add constraint "importvorgang_person_id_foreign" foreign key ("person_id") references "person" ("id") on update cascade on delete set null;',
);
this.addSql(
'alter table "importvorgang" add constraint "importvorgang_rolle_id_foreign" foreign key ("rolle_id") references "rolle" ("id") on update cascade on delete set null;',
);
this.addSql(
'alter table "importvorgang" add constraint "importvorgang_organisation_id_foreign" foreign key ("organisation_id") references "organisation" ("id") on update cascade on delete set null;',
);
}

public override async down(): Promise<void> {

this.addSql('alter table "importvorgang" drop constraint "importvorgang_person_id_foreign";');
this.addSql('alter table "importvorgang" drop constraint "importvorgang_rolle_id_foreign";');
this.addSql('alter table "importvorgang" drop constraint "importvorgang_organisation_id_foreign";');
this.addSql('alter table "importvorgang" rename column "person_id" to "import_by_person_id";');
}
}
14 changes: 14 additions & 0 deletions migrations/Migration20250106093441-S.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Migration } from '@mikro-orm/migrations';

export class Migration20250104001707 extends Migration {
public async up(): Promise<void> {
this.addSql("create type \"import_data_item_status_enum\" as enum ('FAILED', 'SUCCESS', 'PENDING');");
this.addSql('alter table "importdataitem" add column "status" "import_data_item_status_enum" not null;');
}

public override async down(): Promise<void> {
this.addSql('alter table "importdataitem" drop column "data_item_status";');

this.addSql('drop type "import_data_item_status_enum";');
}
}
90 changes: 80 additions & 10 deletions src/modules/import/api/import.controller.integration-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ import { ImportStatus } from '../domain/import.enums.js';
import { StepUpGuard } from '../../authentication/api/steup-up.guard.js';
import { KeycloakAdministrationService } from '../../keycloak-administration/domain/keycloak-admin-client.service.js';
import { ImportVorgangStatusResponse } from './importvorgang-status.response.js';
import { PersonEntity } from '../../person/persistence/person.entity.js';
import { mapAggregateToData } from '../../person/persistence/person.repository.js';
import { ImportDataItemStatus } from '../domain/importDataItem.enum.js';

describe('Import API', () => {
let app: INestApplication;
Expand Down Expand Up @@ -546,8 +549,16 @@ describe('Import API', () => {
);
if (sus instanceof DomainError) throw sus;

const person: PersonEntity = em.create(PersonEntity, mapAggregateToData(DoFactory.createPerson(false)));
await em.persistAndFlush(person);
await em.findOneOrFail(PersonEntity, { id: person.id });

const importVorgang: ImportVorgang<true> = await importVorgangRepository.save(
DoFactory.createImportVorgang(false, { organisationId: schule.id, rolleId: sus.id }),
DoFactory.createImportVorgang(false, {
organisationId: schule.id,
rolleId: sus.id,
importByPersonId: person.id,
}),
);
await importDataRepository.save(
DoFactory.createImportDataItem(false, {
Expand Down Expand Up @@ -581,8 +592,19 @@ describe('Import API', () => {
});

it('should return 500 if the import vorgang has no organisation ID', async () => {
const sus: Rolle<true> | DomainError = await rolleRepo.save(
DoFactory.createRolle(false, {
rollenart: RollenArt.LERN,
merkmale: [],
}),
);
if (sus instanceof DomainError) throw sus;
const importVorgang: ImportVorgang<true> = await importVorgangRepository.save(
DoFactory.createImportVorgang(false, { organisationId: undefined, rolleId: faker.string.uuid() }),
DoFactory.createImportVorgang(false, {
organisationId: undefined,
rolleId: sus.id,
importByPersonId: undefined,
}),
);
const params: ImportvorgangByIdBodyParams = {
importvorgangId: importVorgang.id,
Expand Down Expand Up @@ -625,16 +647,19 @@ describe('Import API', () => {
DoFactory.createImportVorgang(false, {
organisationId: schule.id,
rolleId: sus.id,
importByPersonId: undefined,
status: ImportStatus.FINISHED,
}),
);

const importDataItem: ImportDataItem<true> = await importDataRepository.save(
DoFactory.createImportDataItem(false, {
importvorgangId: importVorgang.id,
klasse: klasse.name,
personalnummer: undefined,
username: faker.internet.userName(),
password: '5ba56bceb34c5b84|6ad72f7a8fa8d98daa7e3f0dc6aa2a82',
status: ImportDataItemStatus.SUCCESS,
}),
);

Expand Down Expand Up @@ -665,7 +690,11 @@ describe('Import API', () => {
describe('/DELETE deleteImportTransaction', () => {
it('should return 204', async () => {
const importVorgang: ImportVorgang<true> = await importVorgangRepository.save(
DoFactory.createImportVorgang(false),
DoFactory.createImportVorgang(false, {
importByPersonId: undefined,
rolleId: undefined,
organisationId: undefined,
}),
);
await importDataRepository.save(
DoFactory.createImportDataItem(false, {
Expand All @@ -684,21 +713,48 @@ describe('Import API', () => {
});

describe('/GET history', () => {
const rolleId: string = faker.string.uuid();
const orgaId1: string = faker.string.uuid();
const orgaId2: string = faker.string.uuid();
let rolleId: string = faker.string.uuid();
let orgaId1: string = faker.string.uuid();
let orgaId2: string = faker.string.uuid();

beforeEach(async () => {
const schule: OrganisationEntity = new OrganisationEntity();
schule.typ = OrganisationsTyp.SCHULE;
schule.name = 'Import Schule';
await em.persistAndFlush(schule);
await em.findOneOrFail(OrganisationEntity, { id: schule.id });
orgaId1 = schule.id;

const schule2: OrganisationEntity = new OrganisationEntity();
schule2.typ = OrganisationsTyp.SCHULE;
await em.persistAndFlush(schule2);
await em.findOneOrFail(OrganisationEntity, { id: schule2.id });
orgaId2 = schule2.id;

const sus: Rolle<true> | DomainError = await rolleRepo.save(
DoFactory.createRolle(false, {
rollenart: RollenArt.LERN,
administeredBySchulstrukturknoten: schule.id,
merkmale: [],
}),
);
if (sus instanceof DomainError) throw sus;

rolleId = sus.id;

await Promise.all([
importVorgangRepository.save(
DoFactory.createImportVorgang(false, {
organisationId: orgaId1,
importByPersonId: undefined,
rolleId: undefined,
}),
),
importVorgangRepository.save(
DoFactory.createImportVorgang(false, {
rolleId: rolleId,
organisationId: orgaId2,
importByPersonId: undefined,
}),
),
]);
Expand Down Expand Up @@ -765,7 +821,11 @@ describe('Import API', () => {
it('should return import history when search by status', async () => {
personPermissionsMock.hasSystemrechteAtRootOrganisation.mockResolvedValue(true);
const startedImport: ImportVorgang<true> = await importVorgangRepository.save(
DoFactory.createImportVorgang(false),
DoFactory.createImportVorgang(false, {
importByPersonId: undefined,
rolleId: undefined,
organisationId: undefined,
}),
);
startedImport.status = ImportStatus.COMPLETED;
await importVorgangRepository.save(startedImport);
Expand All @@ -784,17 +844,27 @@ describe('Import API', () => {
});

describe('/GET importstatus by id', () => {
it('should return 200 OK with import ststus', async () => {
it('should return 200 OK with import status', async () => {
const importVorgang: ImportVorgang<true> = await importVorgangRepository.save(
DoFactory.createImportVorgang(false, { status: ImportStatus.COMPLETED }),
DoFactory.createImportVorgang(false, {
status: ImportStatus.COMPLETED,
importByPersonId: undefined,
rolleId: undefined,
organisationId: undefined,
}),
);

const response: Response = await request(app.getHttpServer() as App)
.get(`/import/${importVorgang.id}/status`)
.send();

expect(response.status).toBe(200);
expect(response.body).toEqual({ status: ImportStatus.COMPLETED } as ImportVorgangStatusResponse);
expect(response.body).toBeInstanceOf(Object);
expect(response.body).toEqual({
dataItemCount: 100,
status: ImportStatus.COMPLETED,
totalDataItemImported: 0,
} as ImportVorgangStatusResponse);
});

it('should return 404 if importvorgang does not exist', async () => {
Expand Down
5 changes: 5 additions & 0 deletions src/modules/import/api/import.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { HttpException } from '@nestjs/common';
import { MissingPermissionsError } from '../../../shared/error/missing-permissions.error.js';
import { ImportvorgangByIdParams } from './importvorgang-by-id.params.js';
import { ImportVorgangRepository } from '../persistence/import-vorgang.repository.js';
import { ImportDataRepository } from '../persistence/import-data.repository.js';

describe('Import API with mocked ImportWorkflow', () => {
let sut: ImportController;
Expand All @@ -46,6 +47,10 @@ describe('Import API with mocked ImportWorkflow', () => {
provide: ImportVorgangRepository,
useValue: createMock<ImportVorgangRepository>(),
},
{
provide: ImportDataRepository,
useValue: createMock<ImportDataRepository>(),
},
ImportController,
],
}).compile();
Expand Down
13 changes: 10 additions & 3 deletions src/modules/import/api/import.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { StepUpGuard } from '../../authentication/api/steup-up.guard.js';
import { EntityNotFoundError } from '../../../shared/error/entity-not-found.error.js';
import { ContentDisposition, ContentType } from '../../../shared/http/http.headers.js';
import { ImportVorgangStatusResponse } from './importvorgang-status.response.js';
import { ImportDataRepository } from '../persistence/import-data.repository.js';

@UseFilters(SchulConnexValidationErrorFilter, new AuthenticationExceptionFilter(), new ImportExceptionFilter())
@ApiTags('import')
Expand All @@ -72,6 +73,7 @@ export class ImportController {
private readonly importWorkflowFactory: ImportWorkflowFactory,
private readonly logger: ClassLogger,
private readonly importVorgangRepository: ImportVorgangRepository,
private readonly importDataRepository: ImportDataRepository,
) {}

@UseGuards(StepUpGuard)
Expand Down Expand Up @@ -293,15 +295,20 @@ export class ImportController {
description: 'Internal server error while getting status for the import transaction by id.',
})
public async getImportStatus(@Param() params: ImportvorgangByIdParams): Promise<ImportVorgangStatusResponse> {
const result: Option<ImportVorgang<true>> = await this.importVorgangRepository.findById(params.importvorgangId);
if (!result) {
const importVorgang: Option<ImportVorgang<true>> = await this.importVorgangRepository.findById(
params.importvorgangId,
);
if (!importVorgang) {
throw SchulConnexErrorMapper.mapSchulConnexErrorToHttpException(
SchulConnexErrorMapper.mapDomainErrorToSchulConnexError(
new EntityNotFoundError('ImportVorgang', params.importvorgangId),
),
);
}

return new ImportVorgangStatusResponse(result);
// Count processed items and add them to the response
const processedItemCount: number = await this.importDataRepository.countProcessedItems(params.importvorgangId);

return new ImportVorgangStatusResponse(importVorgang, processedItemCount);
}
}
10 changes: 9 additions & 1 deletion src/modules/import/api/importvorgang-status.response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@ import { ImportStatus, ImportStatusName } from '../domain/import.enums.js';
import { ImportVorgang } from '../domain/import-vorgang.js';

export class ImportVorgangStatusResponse {
@ApiProperty()
public dataItemCount: number;

@ApiProperty({ enum: ImportStatus, enumName: ImportStatusName })
public status: ImportStatus;

public constructor(importVorgang: ImportVorgang<true>) {
@ApiProperty()
public totalDataItemImported: number;

public constructor(importVorgang: ImportVorgang<true>, processedItemCount: number) {
this.dataItemCount = importVorgang.dataItemCount;
this.status = importVorgang.status;
this.totalDataItemImported = processedItemCount;
}
}
5 changes: 5 additions & 0 deletions src/modules/import/domain/import-data-item.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ImportDataItemStatus } from './importDataItem.enum.js';

export class ImportDataItem<WasPersisted extends boolean> {
private constructor(
public id: Persisted<string, WasPersisted>,
Expand All @@ -11,6 +13,7 @@ export class ImportDataItem<WasPersisted extends boolean> {
public validationErrors?: string[],
public username?: string,
public password?: string,
public status?: ImportDataItemStatus,
) {}

public static construct<WasPersisted extends boolean = false>(
Expand All @@ -25,6 +28,7 @@ export class ImportDataItem<WasPersisted extends boolean> {
validationErrors?: string[],
username?: string,
password?: string,
status?: ImportDataItemStatus,
): ImportDataItem<WasPersisted> {
return new ImportDataItem(
id,
Expand All @@ -38,6 +42,7 @@ export class ImportDataItem<WasPersisted extends boolean> {
validationErrors,
username,
password,
status,
);
}

Expand Down
Loading

0 comments on commit b4562b5

Please sign in to comment.