diff --git a/alfa-client/libs/admin-settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit.formservice.spec.ts b/alfa-client/libs/admin-settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit.formservice.spec.ts index 94778bc50d3155b931f31324eec76b52cc538c2d..6b4b53f7a409b37ef0aaf358cac49b30d7ee6056 100644 --- a/alfa-client/libs/admin-settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit.formservice.spec.ts +++ b/alfa-client/libs/admin-settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit.formservice.spec.ts @@ -13,7 +13,7 @@ import { } from '../../../user/user.model'; import { fakeAsync, tick } from '@angular/core/testing'; import { singleCold, singleHot } from 'libs/tech-shared/src/lib/resource/marbles'; -import { Observable, throwError } from 'rxjs'; +import { lastValueFrom, Observable, throwError } from 'rxjs'; import { hot } from 'jest-marbles'; import { getOrganisationseinheitErrorMessage } from '../../../user/user.util'; @@ -43,31 +43,90 @@ describe('OrganisationseinheitFormService', () => { formService.handleError = jest.fn(); }); - it('should call handle error with service call observable', fakeAsync(() => { - const error: OrganisationseinheitError = createOrganisationseinheitError(); - formService.callService = jest.fn().mockReturnValue(throwError(() => error)); + describe('with successful validation', () => { + beforeEach(() => { + formService.validate = jest.fn().mockReturnValue(true); + }); + it('should call handle error with service call observable', fakeAsync(() => { + const error: OrganisationseinheitError = createOrganisationseinheitError(); + formService.callService = jest.fn().mockReturnValue(throwError(() => error)); + + formService.submit().subscribe(); + tick(); - formService.submit().subscribe(); - tick(); + expect(formService.handleError).toHaveBeenCalledWith(error); + })); - expect(formService.handleError).toHaveBeenCalledWith(error); - })); + it('should emit emit progress as false on error', () => { + const error: OrganisationseinheitError = createOrganisationseinheitError(); + formService.callService = jest.fn().mockReturnValue(hot('a#', { a: true }, error)); - it('should emit emit progress as false on error', () => { - const error: OrganisationseinheitError = createOrganisationseinheitError(); - formService.callService = jest.fn().mockReturnValue(hot('a#', { a: true }, error)); + const progressObservable: Observable<boolean> = formService.submit(); + + expect(progressObservable).toBeObservable(hot('a(b|)', { a: true, b: false })); + }); - const progressObservable: Observable<boolean> = formService.submit(); + it('should return progress observable', () => { + formService.callService = jest.fn().mockReturnValue(singleCold(true)); - expect(progressObservable).toBeObservable(hot('a(b|)', { a: true, b: false })); + const progressObservable: Observable<boolean> = formService.submit(); + + expect(progressObservable).toBeObservable(singleCold(true)); + }); }); + describe('with unsuccessful validation', () => { + beforeEach(() => { + formService.validate = jest.fn().mockReturnValue(false); + }); - it('should return progress observable', () => { - formService.callService = jest.fn().mockReturnValue(singleCold(true)); + it('should return progress observable with false', async () => { + const progressObservable: Observable<boolean> = formService.submit(); - const progressObservable: Observable<boolean> = formService.submit(); + const progress: boolean = await lastValueFrom(progressObservable); - expect(progressObservable).toBeObservable(singleCold(true)); + expect(progress).toBeFalsy(); + }); + }); + }); + + describe('validate', () => { + const hasIdMissingError = () => + formService.form.hasError( + OrganisationseinheitErrorType.ID_MISSING, + OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_IDS_FIELD, + ); + describe('without organisationeinheitIds', () => { + beforeEach(() => { + formService.form.setValue({ + [OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_NAME_FIELD]: + organisationseinheit.name, + [OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_IDS_FIELD]: ',', + }); + }); + + it('should set id-missing error', () => { + formService.validate(); + + expect(hasIdMissingError()).toBeTruthy(); + }); + it('should be invalid', () => { + const valid: boolean = formService.validate(); + + expect(valid).toBeFalsy(); + }); + }); + describe('with organisationeinheitIds', () => { + it('should not set error', () => { + formService.validate(); + + expect(hasIdMissingError()).toBeFalsy(); + }); + + it('should be valid', () => { + const valid: boolean = formService.validate(); + + expect(valid).toBeTruthy(); + }); }); }); diff --git a/alfa-client/libs/admin-settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit.formservice.ts b/alfa-client/libs/admin-settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit.formservice.ts index a5b71af7536c8a763466a3600cd3f67c9487d3dd..5d575985647d3bae7deb8ab6aade2a455b496539 100644 --- a/alfa-client/libs/admin-settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit.formservice.ts +++ b/alfa-client/libs/admin-settings/src/lib/organisationseinheit/organisationseinheit-container/organisationseinheit-form/organisationseinheit.formservice.ts @@ -30,12 +30,16 @@ export class OrganisationseinheitFormservice { } public submit(): Observable<boolean> { - return this.callService().pipe( - catchError((error: OrganisationseinheitError) => { - this.handleError(error); - return of(false); - }), - ); + if (this.validate()) { + return this.callService().pipe( + catchError((error: OrganisationseinheitError) => { + this.handleError(error); + return of(false); + }), + ); + } else { + return of(false); + } } callService(): Observable<boolean> { @@ -57,6 +61,20 @@ export class OrganisationseinheitFormservice { }); } + validate(): boolean { + let valid: boolean = true; + + if (this.getOrganisationseinheitIds().length == 0) { + this.setError(OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_IDS_FIELD, { + errorType: OrganisationseinheitErrorType.ID_MISSING, + detail: '', + }); + valid = false; + } + + return valid; + } + private getName(): string { return this.getStringFromPotentiallyEmptyField( OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_NAME_FIELD, @@ -65,9 +83,7 @@ export class OrganisationseinheitFormservice { private getOrganisationseinheitIds(): string[] { return this.splitOrganisationseinheitIds( - this.getStringFromPotentiallyEmptyField( - OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_IDS_FIELD, - ), + this.form.get(OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_IDS_FIELD).value ?? '', ); } @@ -84,15 +100,17 @@ export class OrganisationseinheitFormservice { error.errorType === OrganisationseinheitErrorType.NAME_CONFLICT || error.errorType === OrganisationseinheitErrorType.NAME_MISSING ) { - const control: AbstractControl = this.form.get( - OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_NAME_FIELD, - ); - control.setErrors({ - [error.errorType]: getOrganisationseinheitErrorMessage(error), - }); + this.setError(OrganisationseinheitFormservice.ORGANISATIONSEINHEIT_NAME_FIELD, error); } } + private setError(controlName: string, error: OrganisationseinheitError): void { + const control: AbstractControl = this.form.get(controlName); + control.setErrors({ + [error.errorType]: getOrganisationseinheitErrorMessage(error), + }); + } + splitOrganisationseinheitIds(organisationseinheitIdsString: string): string[] { return organisationseinheitIdsString .split(',') diff --git a/alfa-client/libs/admin-settings/src/lib/postfach/postfach-container/postfach-form/postfach.formservice.spec.ts b/alfa-client/libs/admin-settings/src/lib/postfach/postfach-container/postfach-form/postfach.formservice.spec.ts index a7adf151bf710ae9e6b0417efdf2334a244ea862..f615c6ce868a39449363184dd6643f96680e3e38 100644 --- a/alfa-client/libs/admin-settings/src/lib/postfach/postfach-container/postfach-form/postfach.formservice.spec.ts +++ b/alfa-client/libs/admin-settings/src/lib/postfach/postfach-container/postfach-form/postfach.formservice.spec.ts @@ -3,9 +3,10 @@ import { PostfachFormService } from './postfach.formservice'; import { mock, Mock, useFromMock } from '@alfa-client/test-utils'; import { PostfachService } from '../../postfach.service'; import { createPostfach, createPostfachResource } from '../../../../../test/postfach/postfach'; +import { PostfachResource } from '../../postfach.model'; +import { createStateResource, StateResource } from '@alfa-client/tech-shared'; import { Postfach } from '../../postfach.model'; import { Observable, of } from 'rxjs'; -import { toResource } from 'libs/tech-shared/test/resource'; import { cold } from 'jest-marbles'; import { createEmptyStateResource } from '@alfa-client/tech-shared'; @@ -27,7 +28,10 @@ describe('PostfachFormService', () => { const postfach: Postfach = createPostfach(); beforeEach(() => { - postfachService.save.mockReturnValue(of(toResource(createPostfachResource()))); + const stateResource: StateResource<PostfachResource> = + createStateResource(createPostfachResource()); + postfachService.save.mockReturnValue(of(stateResource)); + postfachService.get.mockReturnValue(of(stateResource)); formService.form.setValue({ [PostfachFormService.ASBSENDER_GROUP]: { [PostfachFormService.NAME_FIELD]: postfach.absender.name, diff --git a/alfa-client/libs/admin-settings/src/lib/postfach/postfach.service.ts b/alfa-client/libs/admin-settings/src/lib/postfach/postfach.service.ts index 4fa62e36803d3ce3326f0d9b88e5dfe55b15c188..b35600877d2dffe3f97f487ac94b5a295ab04fc0 100644 --- a/alfa-client/libs/admin-settings/src/lib/postfach/postfach.service.ts +++ b/alfa-client/libs/admin-settings/src/lib/postfach/postfach.service.ts @@ -4,7 +4,7 @@ import { Postfach, PostfachResource, PostfachSettingsItem } from './postfach.mod import { ResourceService } from 'libs/tech-shared/src/lib/resource/resource.service'; import { SettingsService } from '../admin-settings.service'; import { ResourceServiceConfig } from 'libs/tech-shared/src/lib/resource/resource.model'; -import { Observable, of, tap } from 'rxjs'; +import { Observable } from 'rxjs'; import { HttpError, StateResource } from '@alfa-client/tech-shared'; import { SettingName } from '../admin-settings.model'; import { PostfachLinkRel } from './postfach.linkrel'; diff --git a/alfa-client/libs/admin-settings/src/lib/user/user.model.ts b/alfa-client/libs/admin-settings/src/lib/user/user.model.ts index 13a1c2cf616cfae08c6a485a50e3d8f2f47c4a84..308e945aa9b0f900ccfc135eeb98f713efc7e5e7 100644 --- a/alfa-client/libs/admin-settings/src/lib/user/user.model.ts +++ b/alfa-client/libs/admin-settings/src/lib/user/user.model.ts @@ -13,6 +13,7 @@ export interface OrganisationseinheitState { export enum OrganisationseinheitErrorType { NAME_CONFLICT = 'name-conflict', NAME_MISSING = 'name-missing', + ID_MISSING = 'id-missing', } export interface OrganisationseinheitError { diff --git a/alfa-client/libs/admin-settings/src/lib/user/user.util.ts b/alfa-client/libs/admin-settings/src/lib/user/user.util.ts index ac5daa3cb50772102f200277a078d48c081ac123..2a1da4ffbdac3353761518988c9c112cbc450ca1 100644 --- a/alfa-client/libs/admin-settings/src/lib/user/user.util.ts +++ b/alfa-client/libs/admin-settings/src/lib/user/user.util.ts @@ -3,6 +3,8 @@ import { OrganisationseinheitError, OrganisationseinheitErrorType } from './user export const KEYCLOAK_ERROR_MESSAGES: { [type: string]: string } = { [OrganisationseinheitErrorType.NAME_CONFLICT]: 'Der Name exisitert bereits.', [OrganisationseinheitErrorType.NAME_MISSING]: 'Bitte den Namen angeben.', + [OrganisationseinheitErrorType.ID_MISSING]: + 'Bitte mindestens eine Organisationseinheit ID angeben.', }; export const KEYCLOAK_CREATE_GROUPS_ERROR_STATUS: { diff --git a/alfa-client/libs/tech-shared/src/lib/resource/resource.service.spec.ts b/alfa-client/libs/tech-shared/src/lib/resource/resource.service.spec.ts index 3311716c86977541f2d114b2ef74d8b5db0cdd38..08d197454b4224dc712dcaf47f1ed3f881fe6c34 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/resource.service.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/resource.service.spec.ts @@ -1,15 +1,16 @@ import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; -import { Observable, lastValueFrom, of, throwError } from 'rxjs'; +import { lastValueFrom, Observable, of, throwError } from 'rxjs'; import { ResourceService } from './resource.service'; import { ResourceRepository } from './resource.repository'; import { LinkRelationName, ResourceServiceConfig, SaveResourceData } from './resource.model'; -import { Resource, getUrl } from '@ngxp/rest'; +import { getUrl, Resource } from '@ngxp/rest'; import { createDummyResource } from 'libs/tech-shared/test/resource'; +import * as ResourceUtil from './resource.util'; import { - StateResource, createEmptyStateResource, createErrorStateResource, createStateResource, + StateResource, } from './resource.util'; import { fakeAsync, tick } from '@angular/core/testing'; import { singleCold, singleHot } from './marbles'; @@ -18,8 +19,6 @@ import { createProblemDetail } from 'libs/tech-shared/test/error'; import { HttpError, ProblemDetail } from '../tech.model'; import { cold } from 'jest-marbles'; -import * as ResourceUtil from './resource.util'; - describe('ResourceService', () => { let service: ResourceService<Resource, Resource>; let config: ResourceServiceConfig<Resource>; @@ -443,6 +442,16 @@ describe('ResourceService', () => { expect(service.handleError).toHaveBeenCalledWith(errorResponse); }); + + it('should update state resource subject', fakeAsync(() => { + service.stateResource.next(createStateResource(resourceWithEditLinkRel)); + repository.save.mockReturnValue(of(loadedResource)); + + service.save(dummyToSave).subscribe(); + tick(); + + expect(service.stateResource.value).toEqual(createStateResource(loadedResource)); + })); }); describe('handleError', () => { diff --git a/alfa-client/libs/tech-shared/src/lib/resource/resource.service.ts b/alfa-client/libs/tech-shared/src/lib/resource/resource.service.ts index c14a661395d83c8b9ff53fd4286ff4a2f7d2211d..a5a705e9d9b0369c2169f00075925c5a67bc4459 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/resource.service.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/resource.service.ts @@ -1,11 +1,10 @@ import { BehaviorSubject, - Observable, catchError, combineLatest, filter, map, - mergeMap, + Observable, of, startWith, tap, @@ -13,21 +12,20 @@ import { } from 'rxjs'; import { ResourceServiceConfig } from './resource.model'; import { - StateResource, createEmptyStateResource, createErrorStateResource, createStateResource, isLoadingRequired, + StateResource, throwErrorOn, } from './resource.util'; -import { Resource, getUrl, hasLink } from '@ngxp/rest'; +import { getUrl, hasLink, Resource } from '@ngxp/rest'; import { ResourceRepository } from './resource.repository'; import { isEqual, isNull } from 'lodash-es'; import { isNotNull } from '../tech.util'; import { HttpErrorResponse } from '@angular/common/http'; import { isUnprocessableEntity } from '../http.util'; import { HttpError, ProblemDetail } from '../tech.model'; -import { isDevMode } from '@angular/core'; /** * B = Type of baseresource @@ -146,11 +144,10 @@ export class ResourceService<B extends Resource, T extends Resource> { public save(toSave: unknown): Observable<StateResource<T | HttpError>> { this.verifyEditLinkRel(); - return this.stateResource.asObservable().pipe( - mergeMap((selectedResource: StateResource<T>) => - this.doSave(selectedResource.resource, toSave), - ), - map((loadedResource: T) => createStateResource(loadedResource)), + const previousResource: T = this.stateResource.value.resource; + return this.doSave(previousResource, toSave).pipe( + tap((loadedResource: T) => this.stateResource.next(createStateResource(loadedResource))), + map(() => this.stateResource.value), catchError((errorResponse: HttpErrorResponse) => this.handleError(errorResponse)), ); }