Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spsh-1574: Fortschrittsanzeige & Bulk-Transaktionen #844

Open
wants to merge 46 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
b77d61d
SPSH-1286: Implemented the repository, entity & aggregate for the his…
phaelcg Nov 27, 2024
fece2b8
SPSH-1286: Refactored the import workflow to update the status of imp…
phaelcg Nov 28, 2024
ff4eff8
SPSH-1286: Implemented unit tests and integration tests
phaelcg Nov 29, 2024
4f9ccae
SPSH-1286: Fixed test coverage
phaelcg Nov 29, 2024
e89db13
SPSH-1286: Fixed code coverage
phaelcg Nov 29, 2024
b841bee
Merge branch 'main' into SPSH-1286
phaelcg Nov 29, 2024
fd0d026
SPSH-1286: Added a reference to the ImportDataItem table and refactor…
phaelcg Nov 29, 2024
28f928c
First draft in progress
phaelcg Dec 2, 2024
174a4cd
SPSH-1553: Workaround for import-workflow
phaelcg Dec 4, 2024
5e5344f
SPSH-1553: Fixed issues with the import-event.handler and added migra…
phaelcg Dec 4, 2024
c8e9768
Merge branch 'main' into SPSH-1553
phaelcg Dec 6, 2024
d9837ee
SPSH-1553: Fixed issued after merge with main
phaelcg Dec 6, 2024
98a9802
SPSH-1553: More code refactoring and foxed tests for the new import w…
phaelcg Dec 10, 2024
af99463
SPSH-1553: Fixed the config loader tests
phaelcg Dec 10, 2024
7e0078a
SPSH-1553: Fixed integration tests
phaelcg Dec 10, 2024
de3c00b
SPSH-1553: Fixed code coverage
phaelcg Dec 10, 2024
50595c1
SPSH-1553: Begrenzung der Anzahl der Nutzer beim Import über die Konf…
phaelcg Dec 11, 2024
958ffcc
SPSH-1553: Fixed issue with last empty line in the CSV file when the …
phaelcg Dec 11, 2024
426bdfc
SPSH-1553: PR Review
phaelcg Dec 12, 2024
95db629
SPSH-1553: Fixed test coverage
phaelcg Dec 12, 2024
d8889ba
Merge branch 'main' into SPSH-1553
casparneumann-cap Dec 12, 2024
4d2b7cf
SPSH-1553: Merged two migration scripts
phaelcg Dec 12, 2024
9402b13
SPSH-1574: Implemented insertMany in import-data-repository
phaelcg Dec 13, 2024
6a3d4c1
SPSH-1553: Reversed mistake in the values.yaml
phaelcg Dec 13, 2024
1a2f74a
Merge branch 'SPSH-1553' into SPSH-1574
phaelcg Dec 13, 2024
0690e7c
SPSH-1553: Added chart configuration and renamed import config variab…
phaelcg Dec 13, 2024
32d7c35
Merge branch 'SPSH-1553' into SPSH-1574
phaelcg Dec 13, 2024
8aceada
SPSH-1574: Implemented create many for the saving of multiple data-it…
phaelcg Dec 16, 2024
eba55c3
SPSH-1574: Fixed the issue with data-items being saved two times
phaelcg Dec 16, 2024
7137875
SPSH-1553: Changed the response type for the import status
phaelcg Dec 16, 2024
5f9f882
Merge branch 'SPSH-1553' into SPSH-1574
phaelcg Dec 16, 2024
37d8e6b
SPSH-1574: Added updateAll in the import-data-repository and fixed th…
phaelcg Dec 17, 2024
d014414
Merge branch 'main' into SPSH-1574
phaelcg Dec 17, 2024
32263fb
SPSH-1574: Renamed updateAll to replaceAll in import-data-repository
phaelcg Dec 17, 2024
1308536
SPSH-1574: Added integration tests and error handling for the replace…
phaelcg Dec 18, 2024
6cd2cf4
SPSH-1574: Fixed sonar cloud error
phaelcg Dec 18, 2024
9eb9b03
Merge branch 'main' into SPSH-1574
phaelcg Dec 18, 2024
6081c6b
SPSH-1574: PR Review
phaelcg Dec 18, 2024
32224a4
New migration for the new column
YoussefBouch Dec 18, 2024
9318e2f
Merge branch 'SPSH-1574' of https://github.com/dBildungsplattform/dbi…
YoussefBouch Dec 18, 2024
53e16e0
Merge branch 'main' of https://github.com/dBildungsplattform/dbildung…
YoussefBouch Dec 19, 2024
720658e
SPSH-1574: Added foreign keys and constrains on updates for the impor…
phaelcg Dec 19, 2024
3f7a8b6
SPSH-1574: Renamed migration script
phaelcg Dec 19, 2024
6c79335
Merge branch 'SPSH-1574' of https://github.com/dBildungsplattform/dbi…
phaelcg Dec 19, 2024
950b018
SPSH-1574: Fixed tests
phaelcg Dec 19, 2024
a2d4ad7
Merge branch 'main' into SPSH-1574
phaelcg Dec 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,414 changes: 1,233 additions & 1,181 deletions migrations/.snapshot-dbildungs-iam-server.json

Large diffs are not rendered by default.

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

export class Migration20241219124504 extends Migration {
public async up(): Promise<void> {
this.addSql('alter table "importvorgang" drop column "import_by_person_id";');

this.addSql(
'alter table "importvorgang" add column "person_id" uuid null, add column "total_data_item_imported" int not null;',
);
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" drop column "person_id", drop column "total_data_item_imported";');

this.addSql('alter table "importvorgang" add column "import_by_person_id" uuid null;');
}
}
88 changes: 78 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,8 @@ 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';

describe('Import API', () => {
let app: INestApplication;
Expand Down Expand Up @@ -546,8 +548,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 +591,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,6 +646,7 @@ describe('Import API', () => {
DoFactory.createImportVorgang(false, {
organisationId: schule.id,
rolleId: sus.id,
importByPersonId: undefined,
status: ImportStatus.FINISHED,
}),
);
Expand Down Expand Up @@ -665,7 +687,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 +710,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 +818,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 +841,28 @@ 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,
totalDataItemImported: 100,
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: 100,
} as ImportVorgangStatusResponse);
});

it('should return 404 if importvorgang does not exist', async () => {
Expand Down
8 changes: 8 additions & 0 deletions 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;

@ApiProperty()
public totalDataItemImported: number;

public constructor(importVorgang: ImportVorgang<true>) {
this.dataItemCount = importVorgang.dataItemCount;
this.status = importVorgang.status;
this.totalDataItemImported = importVorgang.totalDataItemImported;
}
}
30 changes: 24 additions & 6 deletions src/modules/import/domain/import-event-handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { ClassLogger } from '../../../core/logging/class-logger.js';
import { ImportExecutedEvent } from '../../../shared/events/import-executed.event.js';
import { RolleNurAnPassendeOrganisationError } from '../../personenkontext/specification/error/rolle-nur-an-passende-organisation.js';
import { ImportPasswordEncryptor } from './import-password-encryptor.js';
import { ImportDomainError } from './import-domain.error.js';
import { ImportStatus } from './import.enums.js';

describe('ImportEventHandler', () => {
let module: TestingModule;
Expand Down Expand Up @@ -167,13 +169,21 @@ describe('ImportEventHandler', () => {
personenkontextCreationServiceMock.createPersonWithPersonenkontexte.mockResolvedValueOnce(error);
organisationRepoMock.findById.mockResolvedValueOnce(schule);

await sut.handleExecuteImport(event);
const importDomainError: DomainError = new ImportDomainError(
`The creation of person with personenkontexte for the import transaction:${importvorgang.id} failed`,
importvorgang.id,
);

await expect(sut.handleExecuteImport(event)).rejects.toThrowError(importDomainError);

expect(loggerMock.error).toHaveBeenCalledWith(
`System hat versucht einen neuen Benutzer für ${importDataItem.vorname} ${importDataItem.nachname} anzulegen. Fehler: ${error.message}`,
);
expect(importDataRepositoryMock.save).not.toHaveBeenCalled();
expect(importVorgangRepositoryMock.save).not.toHaveBeenCalled();
expect(importVorgangRepositoryMock.save).toHaveBeenCalledWith({
...importvorgang,
status: ImportStatus.FAILED,
});
});

it('should log error if the person has no start password', async () => {
Expand Down Expand Up @@ -206,12 +216,20 @@ describe('ImportEventHandler', () => {
personenkontextCreationServiceMock.createPersonWithPersonenkontexte.mockResolvedValueOnce(pks);
organisationRepoMock.findById.mockResolvedValueOnce(schule);

await sut.handleExecuteImport(event);
const error: DomainError = new ImportDomainError(
`The creation for a password for the person with ID ${person.id} for the import transaction:${importvorgang.id} has failed`,
importvorgang.id,
);

await expect(sut.handleExecuteImport(event)).rejects.toThrowError(error);

expect(person.newPassword).toBeUndefined();
expect(loggerMock.error).toHaveBeenCalledWith(`Person with ID ${person.id} has no start password!`);
expect(importDataRepositoryMock.save).not.toHaveBeenCalled();
expect(importVorgangRepositoryMock.save).not.toHaveBeenCalled();
expect(importVorgangRepositoryMock.save).toHaveBeenCalledWith({
...importvorgang,
status: ImportStatus.FAILED,
});
});

it('should log info if the person and PKs were saved successfully', async () => {
Expand Down Expand Up @@ -258,8 +276,8 @@ describe('ImportEventHandler', () => {
expect(loggerMock.info).toHaveBeenCalledWith(
`System hat einen neuen Benutzer ${person.referrer} (${person.id}) angelegt.`,
);
expect(importDataRepositoryMock.save).toHaveBeenCalled();
expect(importVorgangRepositoryMock.save).toHaveBeenCalledTimes(1);
expect(importDataRepositoryMock.replaceAll).toHaveBeenCalled();
expect(importVorgangRepositoryMock.save).toHaveBeenCalledTimes(2);
});
});
});
Loading
Loading