diff --git a/Jenkinsfile b/Jenkinsfile index f2dec8dd87ec65412cad3393a64933ed1e21b71d..e147e2ff7bab2c8a70de94f403cf2f097c9a4e72 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -264,6 +264,25 @@ pipeline { dependencyCheckPublisher pattern: 'dependency-check-report.xml' } } + + stage ('Trigger Barrierefreiheit Rollout') { + when { + branch 'barrierefreiheit-dev' + } + steps { + script { + FAILED_STAGE = env.STAGE_NAME + + cloneGitopsRepo() + + setNewBarrierefreiheitVersion() + + pushGitopsRepo() + + } + } + } + } post { failure { @@ -295,7 +314,10 @@ String generateHelmChartVersion() { def chartVersion = "${VERSION}" if (isMasterBranch()) { - chartVersion += "-${env.GIT_COMMIT.take(7)}" + chartVersion += getCommitHash() + } + else if (isBarrierefreiheitBranch()) { + chartVersion += "-barrierefreiheit${getCommitHash()}" } else if (!isReleaseBranch()) { chartVersion += "-${env.BRANCH_NAME}" @@ -316,8 +338,8 @@ Void tagAndPushDockerImage(String newTag){ String generateImageTag() { def imageTag = "${env.BRANCH_NAME}-${VERSION}" - if (isMasterBranch()) { - imageTag += "-${env.GIT_COMMIT.take(7)}" + if (isMasterBranch() || isBarrierefreiheitBranch()) { + imageTag += getCommitHash() } return imageTag @@ -386,9 +408,19 @@ Void setNewTestVersion() { } Void setNewGitopsVersion(String environment) { - dir("gitops") { - def envFile = "${environment}/application/values/alfa-values.yaml" + def envFile = "${environment}/application/values/alfa-values.yaml" + def commitMessage = "jenkins rollout ${environment} alfa version ${IMAGE_TAG}"; + setNewGitopsVersion(envFile, commitMessage); +} +Void setNewBarrierefreiheitVersion() { + def envFile = "dev/namespace/namespaces/by-barrierefreiheit-dev.yaml" + def commitMessage = "jenkins rollout ${IMAGE_TAG} for Barrierefreiheit Dev" + setNewGitopsVersion(envFile, commitMessage); +} + +Void setNewGitopsVersion(String envFile, String commitMessage) { + dir("gitops") { def envVersions = readYaml file: envFile envVersions.alfa.image.tag = IMAGE_TAG @@ -396,15 +428,19 @@ Void setNewGitopsVersion(String environment) { writeYaml file: envFile, data: envVersions, overwrite: true - if (hasValuesFileChanged(environment)) { + if (hasValuesFileChanged(envFile)) { sh "git add ${envFile}" - sh "git commit -m 'jenkins rollout ${environment} alfa version ${IMAGE_TAG}'" + sh "git commit -m '${commitMessage}'" } } } -Boolean hasValuesFileChanged(String environment) { - return sh (script: "git status | grep '${environment}/application/values/alfa-values.yaml'", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer +String getCommitHash() { + return "-${env.GIT_COMMIT.take(7)}"; +} + +Boolean hasValuesFileChanged(String envFile) { + return sh (script: "git status | grep '${envFile}'", returnStatus: true) == env.SH_SUCCESS_STATUS_CODE as Integer } Boolean isReleaseBranch() { @@ -415,6 +451,10 @@ Boolean isMasterBranch() { return env.BRANCH_NAME == 'master' } +Boolean isBarrierefreiheitBranch() { + return env.BRANCH_NAME == 'barrierefreiheit-dev' +} + Boolean isReleaseVersion(List versions) { return matchRegexVersion(versions, RELEASE_REGEX) } diff --git a/alfa-client/apps/admin/src/test/helm/deployment_env_test.yaml b/alfa-client/apps/admin/src/test/helm/deployment_env_test.yaml index 444d80b661e454360bdad6eaa0ad76bb63ec5935..6172fd2cdf989a7c932e09c1387d7f28e23db17b 100644 --- a/alfa-client/apps/admin/src/test/helm/deployment_env_test.yaml +++ b/alfa-client/apps/admin/src/test/helm/deployment_env_test.yaml @@ -22,7 +22,7 @@ # unter der Lizenz sind dem Lizenztext zu entnehmen. # -suite: test deployment container environments +suite: test deployment container environments templates: - templates/deployment.yaml set: @@ -73,3 +73,4 @@ tests: content: name: my_test_environment_name value: "A test value" + diff --git a/alfa-client/apps/demo/src/app/app.component.ts b/alfa-client/apps/demo/src/app/app.component.ts index 40addd4c8966f682e403357a673cb578348d5213..fbd58a87093ed57b19c83b1e1c78151e75ca1b66 100644 --- a/alfa-client/apps/demo/src/app/app.component.ts +++ b/alfa-client/apps/demo/src/app/app.component.ts @@ -16,6 +16,7 @@ import { FileIconComponent, FileUploadButtonComponent, InstantSearchComponent, + OfficeIconComponent, RadioButtonCardComponent, SaveIconComponent, SendIconComponent, @@ -26,6 +27,7 @@ import { } from '@ods/system'; import { EMPTY_STRING } from '@alfa-client/tech-shared'; +import { Resource } from '@ngxp/rest'; import { InstantSearchQuery, InstantSearchResult, @@ -53,6 +55,7 @@ import { CustomStepperComponent } from './components/cdk-demo/custom-stepper.com RadioButtonCardComponent, ReactiveFormsModule, InstantSearchComponent, + OfficeIconComponent, SaveIconComponent, SendIconComponent, StampIconComponent, @@ -79,26 +82,22 @@ export class AppComponent { title = 'demo'; - instantSearchItems: InstantSearchResult<unknown>[] = [ + instantSearchItems: InstantSearchResult<Resource>[] = [ { title: 'Landeshauptstadt Kiel - Ordnungsamt, Gewerbe- und Schornsteinfegeraufsicht', description: 'Fabrikstraße 8-10, 24103 Kiel', - data: { resource: 'dummy 1' }, }, { title: 'Amt für Digitalisierung, Breitband und Vermessung Nürnberg Außenstelle Hersbruck', description: 'Rathausmarkt 7, Hersbruck', - data: { resource: 'dummy 2' }, }, { title: 'Amt für Digitalisierung, Breitband und Vermessung Stuttgart', description: 'Rathausmarkt 7, Stuttgart', - data: { resource: 'dummy 3' }, }, { title: 'Amt für Digitalisierung, Breitband und Vermessung Ulm', description: 'Rathausmarkt 7, Ulm', - data: { resource: 'dummy 4' }, }, ]; instantSearchFormControl = new FormControl(EMPTY_STRING); @@ -110,7 +109,7 @@ export class AppComponent { ); } - selectSearchResult(result: InstantSearchResult<unknown>) { + selectSearchResult(result: InstantSearchResult<Resource>) { console.log(result); } diff --git a/alfa-client/libs/collaboration-shared/src/index.ts b/alfa-client/libs/collaboration-shared/src/index.ts index 31025070b90a52d41484bef72d4299235c0903a8..06849e9b26984071d6ee28a69bc0e9315a4ef10e 100644 --- a/alfa-client/libs/collaboration-shared/src/index.ts +++ b/alfa-client/libs/collaboration-shared/src/index.ts @@ -1 +1,4 @@ export * from './lib/collaboration-shared.module'; +export * from './lib/organisations-einheit.linkrel'; +export * from './lib/organisations-einheit.model'; +export * from './lib/organisations-einheit.service'; diff --git a/alfa-client/libs/collaboration-shared/src/lib/collaboration-shared.module.ts b/alfa-client/libs/collaboration-shared/src/lib/collaboration-shared.module.ts index 04110efad0a098ec5eb4b90fcac8fde121093956..1dc63e9f65aede42e64a9f3b2bfea0f295e7a80f 100644 --- a/alfa-client/libs/collaboration-shared/src/lib/collaboration-shared.module.ts +++ b/alfa-client/libs/collaboration-shared/src/lib/collaboration-shared.module.ts @@ -1,9 +1,24 @@ +import { ResourceRepository } from '@alfa-client/tech-shared'; +import { VorgangService } from '@alfa-client/vorgang-shared'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { CollaborationService } from './collaboration.service'; +import { + OrganisationsEinheitResourceSearchService, + createOrganisationsEinheitResourceSearchService, +} from './organisations-einheit-resource-search.service'; +import { OrganisationsEinheitService } from './organisations-einheit.service'; @NgModule({ imports: [CommonModule], - providers: [CollaborationService], + providers: [ + CollaborationService, + OrganisationsEinheitService, + { + provide: OrganisationsEinheitResourceSearchService, + useFactory: createOrganisationsEinheitResourceSearchService, + deps: [ResourceRepository, VorgangService], + }, + ], }) export class CollaborationSharedModule {} diff --git a/alfa-client/libs/collaboration-shared/src/lib/organisations-einheit-resource-search.service.ts b/alfa-client/libs/collaboration-shared/src/lib/organisations-einheit-resource-search.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..23f726f9ed323a5c596e660190d1168e0aea41e5 --- /dev/null +++ b/alfa-client/libs/collaboration-shared/src/lib/organisations-einheit-resource-search.service.ts @@ -0,0 +1,35 @@ +import { + ResourceRepository, + ResourceSearchService, + SearchResourceServiceConfig, + mapToResource, +} from '@alfa-client/tech-shared'; +import { + VorgangResource, + VorgangService, + VorgangWithEingangLinkRel, +} from '@alfa-client/vorgang-shared'; +import { + OrganisationsEinheitListResource, + OrganisationsEinheitResource, +} from './organisations-einheit.model'; + +export class OrganisationsEinheitResourceSearchService extends ResourceSearchService< + VorgangResource, + OrganisationsEinheitListResource, + OrganisationsEinheitResource +> {} + +export function createOrganisationsEinheitResourceSearchService( + repository: ResourceRepository, + vorgangService: VorgangService, +) { + return new ResourceSearchService(buildConfig(vorgangService), repository); +} + +function buildConfig(vorgangService: VorgangService): SearchResourceServiceConfig<VorgangResource> { + return { + baseResource: vorgangService.getVorgangWithEingang().pipe(mapToResource<VorgangResource>()), + searchLinkRel: VorgangWithEingangLinkRel.SEARCH_ORGANISATIONS_EINHEIT, + }; +} diff --git a/alfa-client/libs/collaboration-shared/src/lib/organisations-einheit.linkrel.ts b/alfa-client/libs/collaboration-shared/src/lib/organisations-einheit.linkrel.ts new file mode 100644 index 0000000000000000000000000000000000000000..775f60796051e119cb42f35c368799fe23250981 --- /dev/null +++ b/alfa-client/libs/collaboration-shared/src/lib/organisations-einheit.linkrel.ts @@ -0,0 +1,3 @@ +export enum OrganisationsEinheitListLinkRel { + ORGANISATIONS_EINHEIT_HEADER_LIST = 'organisationsEinheitHeaderList', +} diff --git a/alfa-client/libs/collaboration-shared/src/lib/organisations-einheit.model.ts b/alfa-client/libs/collaboration-shared/src/lib/organisations-einheit.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..aafc23fbbce95dae57f511413b8e1c0dedd936ad --- /dev/null +++ b/alfa-client/libs/collaboration-shared/src/lib/organisations-einheit.model.ts @@ -0,0 +1,20 @@ +import { ListItemResource, ListResource } from '@alfa-client/tech-shared'; +import { Resource } from '@ngxp/rest'; + +export interface OrganisationsEinheit { + name: string; + anschrift: Anschrift; +} + +export interface Anschrift { + strasse: string; + hausnummer: string; + plz: string; + ort: string; +} + +export interface OrganisationsEinheitResource + extends OrganisationsEinheit, + Resource, + ListItemResource {} +export interface OrganisationsEinheitListResource extends ListResource {} diff --git a/alfa-client/libs/collaboration-shared/src/lib/organisations-einheit.service.spec.ts b/alfa-client/libs/collaboration-shared/src/lib/organisations-einheit.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..b1d9a7a6511203304b7367a91df208012eaf3070 --- /dev/null +++ b/alfa-client/libs/collaboration-shared/src/lib/organisations-einheit.service.spec.ts @@ -0,0 +1,105 @@ +import { StateResource, createStateResource } from '@alfa-client/tech-shared'; +import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; +import faker from '@faker-js/faker'; +import { Observable, of } from 'rxjs'; +import { singleColdCompleted } from '../../../tech-shared/test/marbles'; +import { + createOrganisationsEinheitListResource, + createOrganisationsEinheitResource, +} from '../../test/organisations-einheit'; +import { OrganisationsEinheitResourceSearchService } from './organisations-einheit-resource-search.service'; +import { + OrganisationsEinheitListResource, + OrganisationsEinheitResource, +} from './organisations-einheit.model'; +import { OrganisationsEinheitService } from './organisations-einheit.service'; + +jest.mock('./organisations-einheit-resource-search.service'); + +describe('OrganisationsEinheitService', () => { + let service: OrganisationsEinheitService; + + let searchService: Mock<OrganisationsEinheitResourceSearchService>; + + const listResource: OrganisationsEinheitListResource = createOrganisationsEinheitListResource(); + const listStateResource: StateResource<OrganisationsEinheitListResource> = + createStateResource(listResource); + + beforeEach(() => { + searchService = mock(OrganisationsEinheitResourceSearchService); + + service = new OrganisationsEinheitService(useFromMock(searchService)); + }); + + describe('get search result list', () => { + it('should call search service', () => { + service.getSearchResultList(); + + expect(searchService.getResultList).toHaveBeenCalled(); + }); + + it('should return result', (done) => { + searchService.getResultList.mockReturnValue(of(listStateResource)); + + service + .getSearchResultList() + .subscribe((result: StateResource<OrganisationsEinheitListResource>) => { + expect(result).toBe(listStateResource); + done(); + }); + }); + }); + + describe('search', () => { + const searchBy: string = faker.random.word(); + + it('should call search service with search string', () => { + service.search(searchBy); + + expect(searchService.search).toHaveBeenCalledWith(searchBy); + }); + }); + + describe('clear search result', () => { + it('should call search service', () => { + service.clearSearchResult(); + + expect(searchService.clearResultList).toHaveBeenCalledWith(); + }); + }); + + describe('get selected result', () => { + const organisationsEinheitStateResource: StateResource<OrganisationsEinheitResource> = + createStateResource(createOrganisationsEinheitResource()); + + beforeEach(() => { + searchService.getSelectedResult.mockReturnValue(of(organisationsEinheitStateResource)); + }); + + it('should call service', () => { + service.getSelectedResult(); + + expect(searchService.getSelectedResult).toHaveBeenCalled(); + }); + + it('should return result', () => { + const selectedResult$: Observable<StateResource<OrganisationsEinheitResource>> = + service.getSelectedResult(); + + expect(selectedResult$).toBeObservable( + singleColdCompleted(organisationsEinheitStateResource), + ); + }); + }); + + describe('select search result', () => { + const organisationsEinheitResource: OrganisationsEinheitResource = + createOrganisationsEinheitResource(); + + it('should call service', () => { + service.selectSearchResult(organisationsEinheitResource); + + expect(searchService.selectResult).toHaveBeenCalledWith(organisationsEinheitResource); + }); + }); +}); diff --git a/alfa-client/libs/collaboration-shared/src/lib/organisations-einheit.service.ts b/alfa-client/libs/collaboration-shared/src/lib/organisations-einheit.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..64e7ad7d286c23556b3a5fbfa305dfc4bea06d86 --- /dev/null +++ b/alfa-client/libs/collaboration-shared/src/lib/organisations-einheit.service.ts @@ -0,0 +1,33 @@ +import { StateResource } from '@alfa-client/tech-shared'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { OrganisationsEinheitResourceSearchService } from './organisations-einheit-resource-search.service'; +import { + OrganisationsEinheitListResource, + OrganisationsEinheitResource, +} from './organisations-einheit.model'; + +@Injectable() +export class OrganisationsEinheitService { + constructor(private readonly searchService: OrganisationsEinheitResourceSearchService) {} + + public getSearchResultList(): Observable<StateResource<OrganisationsEinheitListResource>> { + return this.searchService.getResultList(); + } + + public search(searchBy: string): void { + this.searchService.search(searchBy); + } + + public clearSearchResult(): void { + this.searchService.clearResultList(); + } + + public getSelectedResult(): Observable<StateResource<OrganisationsEinheitResource>> { + return this.searchService.getSelectedResult(); + } + + public selectSearchResult(organisationsEinheitResource: OrganisationsEinheitResource): void { + this.searchService.selectResult(organisationsEinheitResource); + } +} diff --git a/alfa-client/libs/collaboration-shared/test/organisations-einheit.ts b/alfa-client/libs/collaboration-shared/test/organisations-einheit.ts new file mode 100644 index 0000000000000000000000000000000000000000..4c0f749b773824c841749f7cc5f7851bf7a44134 --- /dev/null +++ b/alfa-client/libs/collaboration-shared/test/organisations-einheit.ts @@ -0,0 +1,49 @@ +import { times } from 'lodash-es'; +import { toResource } from '../../tech-shared/test/resource'; +import { + Anschrift, + OrganisationsEinheit, + OrganisationsEinheitListLinkRel, + OrganisationsEinheitListResource, + OrganisationsEinheitResource, +} from '../src'; + +import { faker } from '@faker-js/faker'; + +export function createAnschrift(): Anschrift { + return { + hausnummer: faker.random.word(), + ort: faker.random.word(), + plz: faker.random.word(), + strasse: faker.random.words(2), + }; +} + +export function createOrganisationsEinheit(): OrganisationsEinheit { + return { + name: faker.random.word(), + anschrift: createAnschrift(), + }; +} + +export function createOrganisationsEinheitResource( + linkRel: string[] = [], +): OrganisationsEinheitResource { + return toResource(createOrganisationsEinheit(), linkRel); +} + +export function createOrganisationsEinheitResources( + linkRelations: string[] = [], +): OrganisationsEinheitResource[] { + return times(10, () => toResource(createOrganisationsEinheitResource(), [...linkRelations])); +} + +export function createOrganisationsEinheitListResource( + resources?: OrganisationsEinheitResource[], + linkRelations: string[] = [], +): OrganisationsEinheitListResource { + return toResource({}, [...linkRelations], { + [OrganisationsEinheitListLinkRel.ORGANISATIONS_EINHEIT_HEADER_LIST]: + resources ? resources : createOrganisationsEinheitResources(), + }); +} diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.html b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.html index f885f5dd33cd4b9fbff5a2ef0deea90831537ac7..f27618ee6c09178f91b15400a43babd3d4088d6a 100644 --- a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.html +++ b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.html @@ -10,8 +10,8 @@ </ng-template> <ng-container *ngIf="isRequestFormVisible$ | async; else anfrageErstellenButton"> - <alfa-collaboration-request-container - data-test-id="collaboration-request-container" - (hideRequestForm)="hideRequestForm()" - ></alfa-collaboration-request-container> + <alfa-collaboration-request-form + data-test-id="collaboration-request-form" + (hide)="hideRequestForm()" + ></alfa-collaboration-request-form> </ng-container> diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.spec.ts b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.spec.ts index d5814a551c15475c3a9b9ca7923dec353a5461d2..cf09233c2433ac766cbc1b600069666d836013b5 100644 --- a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.spec.ts +++ b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-in-vorgang-container.component.spec.ts @@ -12,14 +12,14 @@ import { getDataTestIdAttributeOf, getDataTestIdOf } from 'libs/tech-shared/test import { MockComponent } from 'ng-mocks'; import { of } from 'rxjs'; import { CollaborationInVorgangContainerComponent } from './collaboration-in-vorgang-container.component'; -import { CollaborationRequestContainerComponent } from './collaboration-request-container/collaboration-request-container.component'; +import { CollaborationRequestFormComponent } from './collaboration-request-form/collaboration-request-form.component'; describe('CollaborationInVorgangContainerComponent', () => { let component: CollaborationInVorgangContainerComponent; let fixture: ComponentFixture<CollaborationInVorgangContainerComponent>; const anfrageErstellenButton: string = getDataTestIdAttributeOf('anfrage-erstellen-button'); - const collaborationRequestContainer: string = getDataTestIdOf('collaboration-request-container'); + const collaborationRequestForm: string = getDataTestIdOf('collaboration-request-form'); const service: Mock<CollaborationService> = { ...mock(CollaborationService), @@ -31,7 +31,7 @@ describe('CollaborationInVorgangContainerComponent', () => { declarations: [ CollaborationInVorgangContainerComponent, MockComponent(ButtonComponent), - MockComponent(CollaborationRequestContainerComponent), + MockComponent(CollaborationRequestFormComponent), MockComponent(CollaborationIconComponent), ], providers: [ @@ -97,13 +97,13 @@ describe('CollaborationInVorgangContainerComponent', () => { it('should be shown', () => { fixture.detectChanges(); - existsAsHtmlElement(fixture, collaborationRequestContainer); + existsAsHtmlElement(fixture, collaborationRequestForm); }); it('should call service on hideFormular output', () => { fixture.detectChanges(); - dispatchEventFromFixture(fixture, collaborationRequestContainer, 'hideRequestForm'); + dispatchEventFromFixture(fixture, collaborationRequestForm, 'hide'); expect(service.hideRequestForm).toHaveBeenCalled(); }); @@ -114,7 +114,7 @@ describe('CollaborationInVorgangContainerComponent', () => { fixture.detectChanges(); - notExistsAsHtmlElement(fixture, collaborationRequestContainer); + notExistsAsHtmlElement(fixture, collaborationRequestForm); }); }); }); diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-container.component.html b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-container.component.html deleted file mode 100644 index e3cc2c529513a39ee520a2bbc0e2dcfab2525421..0000000000000000000000000000000000000000 --- a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-container.component.html +++ /dev/null @@ -1,23 +0,0 @@ -<ods-button - variant="outline" - text="Zuständige Stelle auswählen" - dataTestId="zustaendige-stelle-search-button" -> - <ods-search-icon icon /> -</ods-button> - -<div class="my-6"> - <alfa-collaboration-request-form></alfa-collaboration-request-form> -</div> - -<div class="flex items-center gap-6"> - <ods-button text="Zuarbeit anfragen" dataTestId="collaboration-request-send-button"></ods-button> - <ods-button - variant="outline" - text="Abbrechen" - dataTestId="collaboration-request-cancel-button" - (clickEmitter)="hideRequestForm.emit()" - > - <ods-close-icon icon class="fill-primary"></ods-close-icon> - </ods-button> -</div> diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-container.component.spec.ts b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-container.component.spec.ts deleted file mode 100644 index 9cac3e35dd60643ff6dbfe8f61a33bee1a83e634..0000000000000000000000000000000000000000 --- a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-container.component.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { dispatchEventFromFixture } from '@alfa-client/test-utils'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ButtonComponent, CloseIconComponent } from '@ods/system'; -import { SearchIconComponent } from 'libs/design-system/src/lib/icons/search-icon/search-icon.component'; -import { getDataTestIdAttributeOf } from 'libs/tech-shared/test/data-test'; -import { MockComponent } from 'ng-mocks'; -import { CollaborationRequestContainerComponent } from './collaboration-request-container.component'; -import { CollaborationRequestFormComponent } from './collaboration-request-form/collaboration-request-form.component'; - -describe('CollaborationRequestContainerComponent', () => { - let component: CollaborationRequestContainerComponent; - let fixture: ComponentFixture<CollaborationRequestContainerComponent>; - - const cancelButton: string = getDataTestIdAttributeOf('collaboration-request-cancel-button'); - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ - CollaborationRequestContainerComponent, - MockComponent(ButtonComponent), - MockComponent(SearchIconComponent), - MockComponent(CloseIconComponent), - MockComponent(CollaborationRequestFormComponent), - ], - }).compileComponents(); - - fixture = TestBed.createComponent(CollaborationRequestContainerComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('cancel button', () => { - it('should emit hideRequestForm', () => { - const emitSpy = (component.hideRequestForm.emit = jest.fn()); - - dispatchEventFromFixture(fixture, cancelButton, 'clickEmitter'); - - expect(emitSpy).toHaveBeenCalled(); - }); - }); -}); diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-container.component.ts b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-container.component.ts deleted file mode 100644 index e8fa92a4ccb8f6745c556cce327ba4735f87e655..0000000000000000000000000000000000000000 --- a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-container.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component, EventEmitter, Output } from '@angular/core'; - -@Component({ - selector: 'alfa-collaboration-request-container', - templateUrl: './collaboration-request-container.component.html', -}) -export class CollaborationRequestContainerComponent { - @Output() public hideRequestForm: EventEmitter<void> = new EventEmitter<void>(); -} diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-form/collaboration-request-form.component.html b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-form/collaboration-request-form.component.html deleted file mode 100644 index 4a0fc1757205a2790ec6fdc94f39b8470ba2e77d..0000000000000000000000000000000000000000 --- a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-form/collaboration-request-form.component.html +++ /dev/null @@ -1,12 +0,0 @@ -<form [formGroup]="formService.form" class="flex flex-col gap-2"> - <ods-text-editor - label="Titel" - [formControlName]="formServiceClass.FIELD_TITLE" - [isRequired]="true" - ></ods-text-editor> - <ods-textarea-editor - label="Nachricht" - [formControlName]="formServiceClass.FIELD_NACHRICHT" - [isRequired]="true" - ></ods-textarea-editor> -</form> diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-form/collaboration.request.formservice.ts b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-form/collaboration.request.formservice.ts deleted file mode 100644 index 2cb39040c5fdd34ae2d77138aba820a89ae4e17c..0000000000000000000000000000000000000000 --- a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-form/collaboration.request.formservice.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { CommandResource } from '@alfa-client/command-shared'; -import { AbstractFormService, EMPTY_STRING, StateResource } from '@alfa-client/tech-shared'; -import { Injectable } from '@angular/core'; -import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms'; -import { Observable, of } from 'rxjs'; - -@Injectable() -export class CollaborationRequestFormService extends AbstractFormService { - public static readonly FIELD_TITLE = 'titel'; - public static readonly FIELD_NACHRICHT = 'nachricht'; - - constructor(formBuilder: UntypedFormBuilder) { - super(formBuilder); - } - - protected initForm(): UntypedFormGroup { - return this.formBuilder.group({ - [CollaborationRequestFormService.FIELD_TITLE]: new UntypedFormControl(null), - [CollaborationRequestFormService.FIELD_NACHRICHT]: new UntypedFormControl(null), - }); - } - - protected doSubmit(): Observable<StateResource<CommandResource>> { - return of(); - } - - protected getPathPrefix(): string { - return EMPTY_STRING; - } -} diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/collaboration-request-form.component.html b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/collaboration-request-form.component.html new file mode 100644 index 0000000000000000000000000000000000000000..d2cb5eb2d470b1275c22300d5440f78d5465c5ed --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/collaboration-request-form.component.html @@ -0,0 +1,32 @@ +<alfa-organisations-einheit-container + [fieldControl]="formService.form.controls.organisationseinheit" +></alfa-organisations-einheit-container> + +<form [formGroup]="formService.form" class="mt-4 flex flex-col gap-2"> + <ods-text-editor + label="Titel" + [formControlName]="formServiceClass.FIELD_TITLE" + [isRequired]="true" + ></ods-text-editor> + <ods-textarea-editor + label="Nachricht" + [formControlName]="formServiceClass.FIELD_NACHRICHT" + [isRequired]="true" + ></ods-textarea-editor> +</form> + +<div class="mt-4 flex items-center gap-6"> + <ods-button + text="Zuarbeit anfragen" + dataTestId="collaboration-request-send-button" + (clickEmitter)="formService.submit()" + ></ods-button> + <ods-button + variant="outline" + text="Abbrechen" + dataTestId="collaboration-request-cancel-button" + (clickEmitter)="hide.emit()" + > + <ods-close-icon icon class="fill-primary"></ods-close-icon> + </ods-button> +</div> diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-form/collaboration-request-form.component.spec.ts b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/collaboration-request-form.component.spec.ts similarity index 53% rename from alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-form/collaboration-request-form.component.spec.ts rename to alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/collaboration-request-form.component.spec.ts index 7d9a65d558231b860cfc674d52f80b79bc8b66c3..e9d674ae5d0d85b070adaac2278b30d66927cb6a 100644 --- a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-form/collaboration-request-form.component.spec.ts +++ b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/collaboration-request-form.component.spec.ts @@ -1,24 +1,34 @@ -import { Mock, mock } from '@alfa-client/test-utils'; +import { dispatchEventFromFixture } from '@alfa-client/test-utils'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ReactiveFormsModule } from '@angular/forms'; +import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { TextEditorComponent, TextareaEditorComponent } from '@ods/component'; +import { ButtonComponent, CloseIconComponent } from '@ods/system'; +import { getDataTestIdAttributeOf } from 'libs/tech-shared/test/data-test'; import { MockComponent } from 'ng-mocks'; import { CollaborationRequestFormComponent } from './collaboration-request-form.component'; import { CollaborationRequestFormService } from './collaboration.request.formservice'; +import { OrganisationsEinheitContainerComponent } from './organisations-einheit-container/organisations-einheit-container.component'; describe('CollaborationRequestFormComponent', () => { let component: CollaborationRequestFormComponent; let fixture: ComponentFixture<CollaborationRequestFormComponent>; - const formService: Mock<CollaborationRequestFormService> = mock(CollaborationRequestFormService); + const cancelButton: string = getDataTestIdAttributeOf('collaboration-request-cancel-button'); + + const formService: CollaborationRequestFormService = new CollaborationRequestFormService( + new FormBuilder(), + ); beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ReactiveFormsModule], declarations: [ CollaborationRequestFormComponent, + MockComponent(ButtonComponent), + MockComponent(CloseIconComponent), MockComponent(TextEditorComponent), MockComponent(TextareaEditorComponent), + MockComponent(OrganisationsEinheitContainerComponent), ], providers: [ { @@ -36,4 +46,14 @@ describe('CollaborationRequestFormComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + describe('cancel button', () => { + it('should emit hideRequestForm', () => { + const emitSpy: jest.SpyInstance = (component.hide.emit = jest.fn()); + + dispatchEventFromFixture(fixture, cancelButton, 'clickEmitter'); + + expect(emitSpy).toHaveBeenCalled(); + }); + }); }); diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-form/collaboration-request-form.component.ts b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/collaboration-request-form.component.ts similarity index 76% rename from alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-form/collaboration-request-form.component.ts rename to alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/collaboration-request-form.component.ts index 682db1d4f83289c061ff5d733745a42947915530..4dcfcb9ce9658a59b3f87777522b3a99d5cce2d2 100644 --- a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-form/collaboration-request-form.component.ts +++ b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/collaboration-request-form.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, EventEmitter, Output } from '@angular/core'; import { CollaborationRequestFormService } from './collaboration.request.formservice'; @Component({ @@ -7,6 +7,8 @@ import { CollaborationRequestFormService } from './collaboration.request.formser providers: [CollaborationRequestFormService], }) export class CollaborationRequestFormComponent { + @Output() public hide: EventEmitter<void> = new EventEmitter<void>(); + constructor(readonly formService: CollaborationRequestFormService) {} public readonly formServiceClass = CollaborationRequestFormService; diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-form/collaboration.request.formservice.spec.ts b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/collaboration.request.formservice.spec.ts similarity index 100% rename from alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-form/collaboration.request.formservice.spec.ts rename to alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/collaboration.request.formservice.spec.ts diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/collaboration.request.formservice.ts b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/collaboration.request.formservice.ts new file mode 100644 index 0000000000000000000000000000000000000000..eb6812cfda23cd62542bde49af42a42148c2ebba --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/collaboration.request.formservice.ts @@ -0,0 +1,38 @@ +import { CommandResource } from '@alfa-client/command-shared'; +import { AbstractFormService, StateResource } from '@alfa-client/tech-shared'; +import { Injectable } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; +import { ResourceUri } from '@ngxp/rest'; +import { Observable, of } from 'rxjs'; + +@Injectable() +export class CollaborationRequestFormService extends AbstractFormService { + public static readonly FIELD_ORGANISATIONS_EINHEIT: string = 'organisationseinheit'; + public static readonly FIELD_TITLE: string = 'titel'; + public static readonly FIELD_NACHRICHT: string = 'nachricht'; + + private static readonly PATH_PREFIX: string = 'command.body'; + + constructor(formBuilder: FormBuilder) { + super(formBuilder); + } + + protected initForm(): FormGroup { + return this.formBuilder.group({ + [CollaborationRequestFormService.FIELD_ORGANISATIONS_EINHEIT]: new FormControl<ResourceUri>( + null, + ), + [CollaborationRequestFormService.FIELD_TITLE]: new FormControl<string>(null), + [CollaborationRequestFormService.FIELD_NACHRICHT]: new FormControl<string>(null), + }); + } + + protected doSubmit(): Observable<StateResource<CommandResource>> { + console.info('FormValue: ', this.getFormValue()); + return of(); + } + + protected getPathPrefix(): string { + return CollaborationRequestFormService.PATH_PREFIX; + } +} diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit-container.component.html b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit-container.component.html new file mode 100644 index 0000000000000000000000000000000000000000..36cee6fb596565233bad6c63435fe246bd75fe06 --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit-container.component.html @@ -0,0 +1,21 @@ +<ng-container *ngIf="organisationsEinheitResource; else searchButton"> + <div class="flex items-center gap-3"> + <ods-office-icon size="large" class="flex-none" /> + <alfa-organisations-einheit + data-test-id="organisations-einheit-in-collaboration" + [organisationsEinheitResource]="organisationsEinheitResource" + ></alfa-organisations-einheit> + </div> +</ng-container> +<ng-template #searchButton> + <div class="flex items-center gap-3"> + <ods-button + variant="outline" + text="Zuständige Stelle auswählen" + data-test-id="organisations-einheit-search-button" + (clickEmitter)="openSearchDialog()" + > + <ods-search-icon icon /> + </ods-button> + </div> +</ng-template> diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit-container.component.spec.ts b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit-container.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..49f71ddf4203ac77dd0d4f0aa2baa697d896706e --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit-container.component.spec.ts @@ -0,0 +1,168 @@ +import { OrganisationsEinheitResource } from '@alfa-client/collaboration-shared'; +import { + Mock, + dispatchEventFromFixture, + existsAsHtmlElement, + getMockComponent, + mock, +} from '@alfa-client/test-utils'; +import { OzgcloudDialogService } from '@alfa-client/ui'; +import { DialogConfig, DialogRef } from '@angular/cdk/dialog'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormControl } from '@angular/forms'; +import { getUrl } from '@ngxp/rest'; +import { ButtonComponent, OfficeIconComponent } from '@ods/system'; +import { createOrganisationsEinheitResource } from 'libs/collaboration-shared/test/organisations-einheit'; +import { SearchIconComponent } from 'libs/design-system/src/lib/icons/search-icon/search-icon.component'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; +import { MockComponent } from 'ng-mocks'; +import { Subject } from 'rxjs'; +import { SearchOrganisationsEinheitContainerComponent } from '../../../search-organisations-einheit-container/search-organisations-einheit-container.component'; +import { OrganisationsEinheitContainerComponent } from './organisations-einheit-container.component'; +import { OrganisationsEinheitComponent } from './organisations-einheit/organisations-einheit.component'; + +describe('OrganisationsEinheitContainerComponent', () => { + let component: OrganisationsEinheitContainerComponent; + let fixture: ComponentFixture<OrganisationsEinheitContainerComponent>; + + const searchOrganisationsEinheit: string = getDataTestIdOf('organisations-einheit-search-button'); + const organisationsEinheitComp: string = getDataTestIdOf( + 'organisations-einheit-in-collaboration', + ); + + const organisationsEinheitResource: OrganisationsEinheitResource = + createOrganisationsEinheitResource(); + + const dialogService: Mock<OzgcloudDialogService> = mock(OzgcloudDialogService); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + OrganisationsEinheitContainerComponent, + MockComponent(SearchIconComponent), + MockComponent(OfficeIconComponent), + MockComponent(ButtonComponent), + MockComponent(OrganisationsEinheitComponent), + ], + providers: [ + { + provide: OzgcloudDialogService, + useValue: dialogService, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(OrganisationsEinheitContainerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('search zustaendige stelle button', () => { + beforeEach(() => { + component.organisationsEinheitResource = undefined; + }); + + it('should be visible on missing organisationsEinheit', () => { + fixture.detectChanges(); + + existsAsHtmlElement(fixture, searchOrganisationsEinheit); + }); + it('should call openSearchDialog', () => { + component.openSearchDialog = jest.fn(); + + fixture.detectChanges(); + dispatchEventFromFixture(fixture, searchOrganisationsEinheit, 'clickEmitter'); + + expect(component.openSearchDialog).toHaveBeenCalled(); + }); + }); + + describe('organisationsEinheit component', () => { + beforeEach(() => { + component.organisationsEinheitResource = organisationsEinheitResource; + }); + + it('should be visible on existing organisationsEinheit', () => { + fixture.detectChanges(); + + existsAsHtmlElement(fixture, organisationsEinheitComp); + }); + + it('should be called with resource', () => { + fixture.detectChanges(); + + const comp: OrganisationsEinheitComponent = getMockComponent<OrganisationsEinheitComponent>( + fixture, + OrganisationsEinheitComponent, + ); + expect(comp.organisationsEinheitResource).toBe(organisationsEinheitResource); + }); + }); + + describe('open search dialog', () => { + beforeEach(() => { + component.listenToDialogClose = jest.fn(); + }); + + it('should call dialog service', () => { + const DIALOG_CONFIG: DialogConfig = { + backdropClass: ['backdrop-blur-1', 'bg-greybackdrop'], + }; + + component.openSearchDialog(); + + expect(dialogService.openInCallingComponentContext).toHaveBeenCalledWith( + SearchOrganisationsEinheitContainerComponent, + component.viewContainerRef, + null, + DIALOG_CONFIG, + ); + }); + + it('should call listenToDialogClose', () => { + component.openSearchDialog(); + + expect(component.listenToDialogClose).toHaveBeenCalled(); + }); + }); + + describe('listen to dialog close, after closed', () => { + it('should call handleDialogClosed', () => { + const closedSubj: Subject<OrganisationsEinheitResource> = new Subject(); + component.dialogRef = <DialogRef>{ closed: closedSubj.asObservable() }; + component.handleDialogClosed = jest.fn(); + + component.listenToDialogClose(); + closedSubj.next(organisationsEinheitResource); + + expect(component.handleDialogClosed).toHaveBeenCalledWith(organisationsEinheitResource); + }); + }); + + describe('handle dialog closed', () => { + beforeEach(() => { + component.fieldControl = new FormControl(); + }); + + it('should set organisationsEinheit', () => { + component.organisationsEinheitResource = undefined; + + component.handleDialogClosed(organisationsEinheitResource); + + expect(component.organisationsEinheitResource).toBe(organisationsEinheitResource); + }); + + it('should patch fieldControl with resource uri', () => { + const fieldControlPatchSpy: jest.SpyInstance = (component.fieldControl.patchValue = + jest.fn()); + + component.handleDialogClosed(organisationsEinheitResource); + + expect(fieldControlPatchSpy).toHaveBeenCalledWith(getUrl(organisationsEinheitResource)); + }); + }); +}); diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit-container.component.ts b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit-container.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..872efeefb5fb60b6f26fae4c2800bea525b4149c --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit-container.component.ts @@ -0,0 +1,55 @@ +import { + OrganisationsEinheitResource, + OrganisationsEinheitService, +} from '@alfa-client/collaboration-shared'; +import { OzgcloudDialogService } from '@alfa-client/ui'; +import { DialogConfig, DialogRef } from '@angular/cdk/dialog'; +import { Component, Input, ViewContainerRef } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { ResourceUri, getUrl } from '@ngxp/rest'; +import { first } from 'rxjs'; +import { SearchOrganisationsEinheitContainerComponent } from '../../../search-organisations-einheit-container/search-organisations-einheit-container.component'; + +const DIALOG_CONFIG: DialogConfig = { + backdropClass: ['backdrop-blur-1', 'bg-greybackdrop'], +}; + +@Component({ + selector: 'alfa-organisations-einheit-container', + templateUrl: './organisations-einheit-container.component.html', + providers: [OrganisationsEinheitService], +}) +export class OrganisationsEinheitContainerComponent { + @Input() public fieldControl: FormControl<ResourceUri>; + + public organisationsEinheitResource: OrganisationsEinheitResource; + + dialogRef: DialogRef; + + constructor( + private readonly dialogService: OzgcloudDialogService, + readonly viewContainerRef: ViewContainerRef, + ) {} + + public openSearchDialog(): void { + this.dialogRef = + this.dialogService.openInCallingComponentContext<SearchOrganisationsEinheitContainerComponent>( + SearchOrganisationsEinheitContainerComponent, + this.viewContainerRef, + null, + DIALOG_CONFIG, + ); + this.listenToDialogClose(); + } + + listenToDialogClose(): void { + this.dialogRef.closed + .pipe(first()) + .subscribe((result: OrganisationsEinheitResource) => this.handleDialogClosed(result)); + } + + handleDialogClosed(organisationsEinheitResource: OrganisationsEinheitResource): void { + this.organisationsEinheitResource = organisationsEinheitResource; + this.fieldControl.patchValue(getUrl(organisationsEinheitResource)); + } +} diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit/organisations-einheit.component.html b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit/organisations-einheit.component.html new file mode 100644 index 0000000000000000000000000000000000000000..57d9280a6f6c3909e5d5c4494a245a803517d7a3 --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit/organisations-einheit.component.html @@ -0,0 +1,2 @@ +<p class="font-bold">{{ name }}</p> +<p>{{ address }}</p> diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit/organisations-einheit.component.spec.ts b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit/organisations-einheit.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..490dab5d431c35493aa38e8c323ea990706c0ec3 --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit/organisations-einheit.component.spec.ts @@ -0,0 +1,58 @@ +import { OrganisationsEinheitResource } from '@alfa-client/collaboration-shared'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { createOrganisationsEinheitResource } from 'libs/collaboration-shared/test/organisations-einheit'; +import { OrganisationsEinheitComponent } from './organisations-einheit.component'; + +describe('OrganisationsEinheitComponent', () => { + let component: OrganisationsEinheitComponent; + let fixture: ComponentFixture<OrganisationsEinheitComponent>; + + const organisationsEinheitResource: OrganisationsEinheitResource = + createOrganisationsEinheitResource(); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [OrganisationsEinheitComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(OrganisationsEinheitComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('set organisationsEinheit', () => { + it('should call update by organisationsEinehit', () => { + component.updateByOrganisationsEinheit = jest.fn(); + + component.organisationsEinheitResource = organisationsEinheitResource; + + expect(component.updateByOrganisationsEinheit).toHaveBeenCalledWith( + organisationsEinheitResource, + ); + }); + }); + + describe('update by organisationsEinheit', () => { + it('should set name', () => { + component.name = null; + + component.updateByOrganisationsEinheit(organisationsEinheitResource); + + expect(component.name).toEqual(organisationsEinheitResource.name); + }); + + it('should set address', () => { + component.address = null; + + component.updateByOrganisationsEinheit(organisationsEinheitResource); + + expect(component.address).toEqual( + `${organisationsEinheitResource.anschrift.strasse} ${organisationsEinheitResource.anschrift.hausnummer}, ${organisationsEinheitResource.anschrift.plz} ${organisationsEinheitResource.anschrift.ort}`, + ); + }); + }); +}); diff --git a/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit/organisations-einheit.component.ts b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit/organisations-einheit.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..ff94e74f550eee641eaf4645b166838d5048badb --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit/organisations-einheit.component.ts @@ -0,0 +1,26 @@ +import { Anschrift, OrganisationsEinheitResource } from '@alfa-client/collaboration-shared'; +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'alfa-organisations-einheit', + templateUrl: './organisations-einheit.component.html', +}) +export class OrganisationsEinheitComponent { + @Input() public set organisationsEinheitResource( + organisationsEinheit: OrganisationsEinheitResource, + ) { + this.updateByOrganisationsEinheit(organisationsEinheit); + } + + public name: string; + public address: string; + + updateByOrganisationsEinheit(organisationsEinheit: OrganisationsEinheitResource): void { + this.name = organisationsEinheit.name; + this.address = this.buildAddress(organisationsEinheit.anschrift); + } + + private buildAddress(anschrift: Anschrift): string { + return `${anschrift.strasse} ${anschrift.hausnummer}, ${anschrift.plz} ${anschrift.ort}`; + } +} diff --git a/alfa-client/libs/collaboration/src/lib/collaboration.module.ts b/alfa-client/libs/collaboration/src/lib/collaboration.module.ts index 89b35e5e8754d164880275f7177a170b7e348b05..bbe069e5b79e331041e43cbb62ed661f085e2d72 100644 --- a/alfa-client/libs/collaboration/src/lib/collaboration.module.ts +++ b/alfa-client/libs/collaboration/src/lib/collaboration.module.ts @@ -1,4 +1,5 @@ import { CollaborationSharedModule } from '@alfa-client/collaboration-shared'; +import { TechSharedModule } from '@alfa-client/tech-shared'; import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -7,17 +8,23 @@ import { ButtonComponent, CloseIconComponent, CollaborationIconComponent, + InstantSearchComponent, + OfficeIconComponent, SaveIconComponent, + SearchIconComponent, } from '@ods/system'; -import { SearchIconComponent } from 'libs/design-system/src/lib/icons/search-icon/search-icon.component'; import { CollaborationInVorgangContainerComponent } from './collaboration-in-vorgang-container/collaboration-in-vorgang-container.component'; -import { CollaborationRequestContainerComponent } from './collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-container.component'; -import { CollaborationRequestFormComponent } from './collaboration-in-vorgang-container/collaboration-request-container/collaboration-request-form/collaboration-request-form.component'; +import { CollaborationRequestFormComponent } from './collaboration-in-vorgang-container/collaboration-request-form/collaboration-request-form.component'; +import { OrganisationsEinheitContainerComponent } from './collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit-container.component'; +import { OrganisationsEinheitComponent } from './collaboration-in-vorgang-container/collaboration-request-form/organisations-einheit-container/organisations-einheit/organisations-einheit.component'; +import { SearchOrganisationsEinheitContainerComponent } from './search-organisations-einheit-container/search-organisations-einheit-container.component'; +import { SearchOrganisationsEinheitFormComponent } from './search-organisations-einheit-container/search-organisations-einheit-form/search-organisations-einheit-form.component'; @NgModule({ imports: [ CommonModule, ButtonComponent, + OfficeIconComponent, SaveIconComponent, CloseIconComponent, SearchIconComponent, @@ -27,11 +34,16 @@ import { CollaborationRequestFormComponent } from './collaboration-in-vorgang-co TextareaEditorComponent, FormsModule, ReactiveFormsModule, + InstantSearchComponent, + TechSharedModule, ], declarations: [ CollaborationInVorgangContainerComponent, - CollaborationRequestContainerComponent, CollaborationRequestFormComponent, + SearchOrganisationsEinheitContainerComponent, + SearchOrganisationsEinheitFormComponent, + OrganisationsEinheitContainerComponent, + OrganisationsEinheitComponent, ], exports: [CollaborationInVorgangContainerComponent], }) diff --git a/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-container.component.html b/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-container.component.html new file mode 100644 index 0000000000000000000000000000000000000000..9af9814d5c4b8c0e740290016e4e3c8bdaecd4db --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-container.component.html @@ -0,0 +1,19 @@ +<div class="my-32 flex h-screen flex-col gap-2"> + <div class="flex gap-48 py-6 lg:gap-96"> + <h1 class="text-xl font-bold text-primary">Zuständige Stelle auswählen</h1> + <ods-button variant="icon" size="fit" (clickEmitter)="closeDialog()"> + <ods-close-icon class="fill-primary" icon /> + </ods-button> + </div> + <alfa-search-organisations-einheit-form + *ngIf="organisationsEinheitStateListResource$ | async as organisationsEinheitStateListResource" + data-test-id="search-organisations-einheit" + [organisationsEinheiten]=" + organisationsEinheitStateListResource.resource + | toEmbeddedResources: organisationsEinheitListLinkRel.ORGANISATIONS_EINHEIT_HEADER_LIST + " + (search)="search($event)" + (selectSearchResult)="selectSearchResult($event)" + (clearSearchResult)="clearSearchResult()" + ></alfa-search-organisations-einheit-form> +</div> diff --git a/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-container.component.spec.ts b/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-container.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..21d770d9c3bd231de7d5d039c5a29f079c04476b --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-container.component.spec.ts @@ -0,0 +1,199 @@ +import { + OrganisationsEinheitListResource, + OrganisationsEinheitResource, + OrganisationsEinheitService, +} from '@alfa-client/collaboration-shared'; +import { + StateResource, + ToEmbeddedResourcesPipe, + createStateResource, +} from '@alfa-client/tech-shared'; +import { + EventData, + Mock, + dialogRefMock, + getMockComponent, + mock, + triggerEvent, +} from '@alfa-client/test-utils'; +import { DialogRef } from '@angular/cdk/dialog'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import faker from '@faker-js/faker'; +import { ButtonComponent, CloseIconComponent } from '@ods/system'; +import { + createOrganisationsEinheitListResource, + createOrganisationsEinheitResource, + createOrganisationsEinheitResources, +} from 'libs/collaboration-shared/test/organisations-einheit'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; +import { MockComponent } from 'ng-mocks'; +import { of } from 'rxjs'; +import { SearchOrganisationsEinheitContainerComponent } from './search-organisations-einheit-container.component'; +import { SearchOrganisationsEinheitFormComponent } from './search-organisations-einheit-form/search-organisations-einheit-form.component'; + +describe('SearchOrganisationsEinheitContainerComponent', () => { + let component: SearchOrganisationsEinheitContainerComponent; + let fixture: ComponentFixture<SearchOrganisationsEinheitContainerComponent>; + + const searchOrganisationsEinheitComp: string = getDataTestIdOf('search-organisations-einheit'); + + const service: Mock<OrganisationsEinheitService> = mock(OrganisationsEinheitService); + + const organisationsEinheitResource: OrganisationsEinheitResource = + createOrganisationsEinheitResource(); + const organisationsEinheitResources: OrganisationsEinheitResource[] = + createOrganisationsEinheitResources(); + const organisationsEinheitListResource: OrganisationsEinheitListResource = + createOrganisationsEinheitListResource(organisationsEinheitResources); + const organisationsEinheitStateListResource: StateResource<OrganisationsEinheitListResource> = + createStateResource(organisationsEinheitListResource); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + SearchOrganisationsEinheitContainerComponent, + ToEmbeddedResourcesPipe, + MockComponent(SearchOrganisationsEinheitFormComponent), + MockComponent(ButtonComponent), + MockComponent(CloseIconComponent), + ], + providers: [ + { + provide: OrganisationsEinheitService, + useValue: service, + }, + { + provide: DialogRef, + useValue: dialogRefMock, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(SearchOrganisationsEinheitContainerComponent); + component = fixture.componentInstance; + component.organisationsEinheitStateListResource$ = of(organisationsEinheitStateListResource); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('ngOnInit', () => { + it('should call service', () => { + component.ngOnInit(); + + expect(service.getSearchResultList).toHaveBeenCalled(); + }); + }); + + describe('search organisationsEinheit component', () => { + beforeEach(() => { + component.organisationsEinheitStateListResource$ = of(organisationsEinheitStateListResource); + fixture.detectChanges(); + }); + + it('should be called with organisationsEinheiten', () => { + const comp: SearchOrganisationsEinheitFormComponent = + getMockComponent<SearchOrganisationsEinheitFormComponent>( + fixture, + SearchOrganisationsEinheitFormComponent, + ); + + expect(comp.organisationsEinheiten).toEqual(organisationsEinheitResources); + }); + + it('should call search on openSearchDialog output', () => { + component.search = jest.fn(); + const searchBy: string = faker.random.word(); + const eventData: EventData<SearchOrganisationsEinheitContainerComponent> = { + fixture, + elementSelector: searchOrganisationsEinheitComp, + name: 'search', + data: searchBy, + }; + + triggerEvent(eventData); + + expect(component.search).toHaveBeenCalledWith(searchBy); + }); + + it('should call selectSearchResult on selectSearchResult output', () => { + component.selectSearchResult = jest.fn(); + + const eventData: EventData<SearchOrganisationsEinheitContainerComponent> = { + fixture, + elementSelector: searchOrganisationsEinheitComp, + name: 'selectSearchResult', + data: organisationsEinheitResource, + }; + + triggerEvent(eventData); + + expect(component.selectSearchResult).toHaveBeenCalledWith(organisationsEinheitResource); + }); + + it('should call clearSearchResult', () => { + component.clearSearchResult = jest.fn(); + + const eventData: EventData<SearchOrganisationsEinheitContainerComponent> = { + fixture, + elementSelector: searchOrganisationsEinheitComp, + name: 'clearSearchResult', + data: organisationsEinheitResource, + }; + + triggerEvent(eventData); + + expect(component.clearSearchResult).toHaveBeenCalled(); + }); + }); + + describe('search', () => { + const searchBy: string = faker.random.word(); + + it('should call service', () => { + component.search(searchBy); + + expect(service.search).toHaveBeenCalledWith(searchBy); + }); + }); + + describe('select search result', () => { + it('should call service', () => { + component.selectSearchResult(organisationsEinheitResource); + + expect(service.clearSearchResult).toHaveBeenCalled(); + }); + + it('should close dialog', () => { + component.selectSearchResult(organisationsEinheitResource); + + expect(dialogRefMock.close).toHaveBeenCalledWith(organisationsEinheitResource); + }); + }); + + describe('clear search result', () => { + it('should call service', () => { + component.clearSearchResult(); + + expect(service.clearSearchResult).toHaveBeenCalled(); + }); + }); + + describe('close dialog', () => { + it('should clear search result', () => { + component.clearSearchResult = jest.fn(); + + component.closeDialog(); + + expect(component.clearSearchResult).toHaveBeenCalled(); + }); + + it('should close dialog', () => { + component.closeDialog(); + + expect(dialogRefMock.close).toHaveBeenCalled(); + }); + }); +}); diff --git a/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-container.component.ts b/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-container.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..b27e9b6358068fe7e7871cdc27dc8d434abd8ad3 --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-container.component.ts @@ -0,0 +1,51 @@ +import { + OrganisationsEinheitListLinkRel, + OrganisationsEinheitService, +} from '@alfa-client/collaboration-shared'; +import { StateResource } from '@alfa-client/tech-shared'; +import { DialogRef } from '@angular/cdk/dialog'; +import { Component, OnInit } from '@angular/core'; +import { + OrganisationsEinheitListResource, + OrganisationsEinheitResource, +} from 'libs/collaboration-shared/src/lib/organisations-einheit.model'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'alfa-search-organisations-einheit-container', + templateUrl: './search-organisations-einheit-container.component.html', +}) +export class SearchOrganisationsEinheitContainerComponent implements OnInit { + public organisationsEinheitStateListResource$: Observable< + StateResource<OrganisationsEinheitListResource> + >; + + public readonly organisationsEinheitListLinkRel = OrganisationsEinheitListLinkRel; + + constructor( + private readonly service: OrganisationsEinheitService, + private readonly dialogRef: DialogRef, + ) {} + + ngOnInit(): void { + this.organisationsEinheitStateListResource$ = this.service.getSearchResultList(); + } + + public search(searchBy: string): void { + this.service.search(searchBy); + } + + public selectSearchResult(organisationsEinheit: OrganisationsEinheitResource): void { + this.service.clearSearchResult(); + this.dialogRef.close(organisationsEinheit); + } + + public clearSearchResult(): void { + this.service.clearSearchResult(); + } + + public closeDialog(): void { + this.clearSearchResult(); + this.dialogRef.close(); + } +} diff --git a/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-form/search-organisations-einheit-form.component.html b/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-form/search-organisations-einheit-form.component.html new file mode 100644 index 0000000000000000000000000000000000000000..7252665d682c7686f3ae4b63e4cce1567edf4f9d --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-form/search-organisations-einheit-form.component.html @@ -0,0 +1,12 @@ +<form [formGroup]="formService.form"> + <ods-instant-search + data-test-id="search" + placeholder="Name des Amts oder Adresse eingeben" + [control]="formService.form.controls.search" + [searchResults]="searchResults" + (searchResultSelected)="selectSearchResult.emit($event.data)" + (searchQueryChanged)="search.emit($event.searchBy)" + (searchQueryCleared)="clearSearchResult.emit()" + (searchResultClosed)="clearSearchResult.emit()" + ></ods-instant-search> +</form> diff --git a/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-form/search-organisations-einheit-form.component.spec.ts b/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-form/search-organisations-einheit-form.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e93e7403bfa65080636bf44355f942ab224f063f --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-form/search-organisations-einheit-form.component.spec.ts @@ -0,0 +1,183 @@ +import { OrganisationsEinheitResource } from '@alfa-client/collaboration-shared'; +import { EventData, Mock, getMockComponent, mock, triggerEvent } from '@alfa-client/test-utils'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import faker from '@faker-js/faker'; +import { InstantSearchComponent } from '@ods/system'; +import { createOrganisationsEinheitResource } from 'libs/collaboration-shared/test/organisations-einheit'; +import { InstantSearchResult } from 'libs/design-system/src/lib/instant-search/instant-search/instant-search.model'; +import { createInstantSearchResult } from 'libs/design-system/src/test/search'; +import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; +import { MockComponent } from 'ng-mocks'; +import { SearchOrganisationsEinheitFormService } from '../search-organisations-einheit.formservice'; +import { SearchOrganisationsEinheitFormComponent } from './search-organisations-einheit-form.component'; + +describe('SearchOrganisationsEinheitFormComponent', () => { + let component: SearchOrganisationsEinheitFormComponent; + let fixture: ComponentFixture<SearchOrganisationsEinheitFormComponent>; + + const searchComp: string = getDataTestIdOf('search'); + + const formService: Mock<SearchOrganisationsEinheitFormService> = mock( + SearchOrganisationsEinheitFormService, + ); + + const instantSearchResult: InstantSearchResult<OrganisationsEinheitResource> = + createInstantSearchResult<OrganisationsEinheitResource>(); + const organisationsEinheitResource: OrganisationsEinheitResource = + createOrganisationsEinheitResource(); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ReactiveFormsModule], + declarations: [ + SearchOrganisationsEinheitFormComponent, + MockComponent(InstantSearchComponent), + ], + providers: [ + { + provide: SearchOrganisationsEinheitFormService, + useValue: formService, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(SearchOrganisationsEinheitFormComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('set organisationsEinheiten', () => { + it('should map organisationsEinheitResources', () => { + component.mapOrganisationsEinheitResources = jest.fn(); + + component.organisationsEinheiten = [organisationsEinheitResource]; + + expect(component.mapOrganisationsEinheitResources).toHaveBeenCalledWith([ + organisationsEinheitResource, + ]); + }); + }); + + describe('map organisationsEinheit Resources', () => { + it('should call mapToInstantSearchResult', () => { + component.mapToInstantantSearchResult = jest.fn(); + + component.mapOrganisationsEinheitResources([organisationsEinheitResource]); + + expect(component.mapToInstantantSearchResult).toHaveBeenCalledWith( + organisationsEinheitResource, + ); + }); + + it('should set searchResults', () => { + component.mapToInstantantSearchResult = jest.fn().mockReturnValue(instantSearchResult); + + component.mapOrganisationsEinheitResources([organisationsEinheitResource]); + + expect(component.searchResults).toEqual([instantSearchResult]); + }); + }); + + describe('map to instand search result', () => { + it('should map titel', () => { + const instantSearchResult: InstantSearchResult<OrganisationsEinheitResource> = + component.mapToInstantantSearchResult(organisationsEinheitResource); + + expect(instantSearchResult.title).toBe(organisationsEinheitResource.name); + }); + it('should map description', () => { + const instantSearchResult: InstantSearchResult<OrganisationsEinheitResource> = + component.mapToInstantantSearchResult(organisationsEinheitResource); + + const expectedDescription: string = `${organisationsEinheitResource.anschrift.strasse} ${organisationsEinheitResource.anschrift.plz} ${organisationsEinheitResource.anschrift.ort}`; + expect(instantSearchResult.description).toBe(expectedDescription); + }); + + it('should map data', () => { + const instantSearchResult: InstantSearchResult<OrganisationsEinheitResource> = + component.mapToInstantantSearchResult(organisationsEinheitResource); + + expect(instantSearchResult.data).toBe(organisationsEinheitResource); + }); + }); + + describe('instant search component', () => { + it('should be called with search results', () => { + component.searchResults = [instantSearchResult]; + + fixture.detectChanges(); + + expect(getInstantSearchComponent().searchResults).toEqual([instantSearchResult]); + }); + + function getInstantSearchComponent(): InstantSearchComponent { + return getMockComponent<InstantSearchComponent>(fixture, InstantSearchComponent); + } + + it('should emit selected search result on searchResultSelected output', () => { + const selectSearchResultSpy: jest.SpyInstance = (component.selectSearchResult.emit = + jest.fn()); + const eventData: EventData<SearchOrganisationsEinheitFormComponent> = { + fixture, + elementSelector: searchComp, + name: 'searchResultSelected', + data: { data: organisationsEinheitResource }, + }; + + triggerEvent(eventData); + + expect(selectSearchResultSpy).toHaveBeenCalledWith(organisationsEinheitResource); + }); + + it('should emit search on searchQueryChanged output', () => { + const searchSpy: jest.SpyInstance = (component.search.emit = jest.fn()); + const searchBy: string = faker.random.word(); + const eventData: EventData<SearchOrganisationsEinheitFormComponent> = { + fixture, + elementSelector: searchComp, + name: 'searchQueryChanged', + data: { searchBy }, + }; + + triggerEvent(eventData); + + expect(searchSpy).toHaveBeenCalledWith(searchBy); + }); + + describe('should emit clear search result', () => { + let clearSearchResultSpy: jest.SpyInstance; + let eventData: EventData<SearchOrganisationsEinheitFormComponent>; + + beforeEach(() => { + clearSearchResultSpy = component.clearSearchResult.emit = jest.fn(); + eventData = { + fixture, + elementSelector: searchComp, + name: 'TBD', + data: { searchBy: faker.random.word() }, + }; + }); + + it('on searchResultClosed output', () => { + eventData = { ...eventData, name: 'searchResultClosed' }; + + triggerEvent(eventData); + + expect(clearSearchResultSpy).toHaveBeenCalledWith(); + }); + + it('on searchQueryCleared output', () => { + eventData = { ...eventData, name: 'searchResultClosed' }; + + triggerEvent(eventData); + + expect(clearSearchResultSpy).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-form/search-organisations-einheit-form.component.ts b/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-form/search-organisations-einheit-form.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..2e49ca660d004820de961dd252ef7ed156040a1a --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit-form/search-organisations-einheit-form.component.ts @@ -0,0 +1,41 @@ +import { OrganisationsEinheitResource } from '@alfa-client/collaboration-shared'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { InstantSearchResult } from '@ods/system'; +import { SearchOrganisationsEinheitFormService } from '../search-organisations-einheit.formservice'; + +@Component({ + selector: 'alfa-search-organisations-einheit-form', + templateUrl: './search-organisations-einheit-form.component.html', + providers: [SearchOrganisationsEinheitFormService], +}) +export class SearchOrganisationsEinheitFormComponent { + @Input() set organisationsEinheiten(organisationsEinheiten: OrganisationsEinheitResource[]) { + this.mapOrganisationsEinheitResources(organisationsEinheiten); + } + + @Output() public search: EventEmitter<string> = new EventEmitter(); + @Output() public selectSearchResult: EventEmitter<OrganisationsEinheitResource> = + new EventEmitter(); + @Output() public clearSearchResult: EventEmitter<string> = new EventEmitter(); + + public searchResults: InstantSearchResult<OrganisationsEinheitResource>[]; + + constructor(public formService: SearchOrganisationsEinheitFormService) {} + + mapOrganisationsEinheitResources(organisationsEinheiten: OrganisationsEinheitResource[]): void { + this.searchResults = organisationsEinheiten.map( + (organisationsEinheiten: OrganisationsEinheitResource) => + this.mapToInstantantSearchResult(organisationsEinheiten), + ); + } + + mapToInstantantSearchResult( + organisationsEinheit: OrganisationsEinheitResource, + ): InstantSearchResult<OrganisationsEinheitResource> { + return <any>{ + title: organisationsEinheit.name, + description: `${organisationsEinheit.anschrift.strasse} ${organisationsEinheit.anschrift.plz} ${organisationsEinheit.anschrift.ort}`, + data: organisationsEinheit, + }; + } +} diff --git a/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit.formservice.ts b/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit.formservice.ts new file mode 100644 index 0000000000000000000000000000000000000000..2b63e2097a660615148dce5468d7b017976b64ef --- /dev/null +++ b/alfa-client/libs/collaboration/src/lib/search-organisations-einheit-container/search-organisations-einheit.formservice.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup } from '@angular/forms'; +import { ResourceUri } from '@ngxp/rest'; + +@Injectable() +export class SearchOrganisationsEinheitFormService { + public form: FormGroup; + + public readonly SEARCH_FIELD: string = 'search'; + + constructor(private formBuilder: FormBuilder) { + this.initForm(); + } + + private initForm(): void { + this.form = this.formBuilder.group({ + [this.SEARCH_FIELD]: new FormControl<ResourceUri>(null), + }); + } +} diff --git a/alfa-client/libs/design-system/src/index.ts b/alfa-client/libs/design-system/src/index.ts index 9a59b13cbc25c6174275daa3727bdd8388fb0dc4..0c83b3d1ba9df2ab88f88227a9197f681970ca18 100644 --- a/alfa-client/libs/design-system/src/index.ts +++ b/alfa-client/libs/design-system/src/index.ts @@ -18,9 +18,12 @@ export * from './lib/icons/collaboration-icon/collaboration-icon.component'; export * from './lib/icons/exclamation-icon/exclamation-icon.component'; export * from './lib/icons/file-icon/file-icon.component'; export * from './lib/icons/iconVariants'; +export * from './lib/icons/office-icon/office-icon.component'; export * from './lib/icons/save-icon/save-icon.component'; +export * from './lib/icons/search-icon/search-icon.component'; export * from './lib/icons/send-icon/send-icon.component'; export * from './lib/icons/spinner-icon/spinner-icon.component'; export * from './lib/icons/stamp-icon/stamp-icon.component'; export * from './lib/instant-search/instant-search/instant-search.component'; +export * from './lib/instant-search/instant-search/instant-search.model'; export * from './lib/testbtn/testbtn.component'; diff --git a/alfa-client/libs/design-system/src/lib/icons/office-icon/office-icon.component.spec.ts b/alfa-client/libs/design-system/src/lib/icons/office-icon/office-icon.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..b41f00f7375d9ef6362471bda4734fa271f18ead --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/icons/office-icon/office-icon.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { OfficeIconComponent } from './office-icon.component'; + +describe('SaveIconComponent', () => { + let component: OfficeIconComponent; + let fixture: ComponentFixture<OfficeIconComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [OfficeIconComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(OfficeIconComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/alfa-client/libs/design-system/src/lib/icons/office-icon/office-icon.component.ts b/alfa-client/libs/design-system/src/lib/icons/office-icon/office-icon.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..25bdbb57d5011b3135a55be56cae8121c1611485 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/icons/office-icon/office-icon.component.ts @@ -0,0 +1,28 @@ +import { NgClass } from '@angular/common'; +import { Component, Input } from '@angular/core'; +import { twMerge } from 'tailwind-merge'; + +import { IconVariants, iconVariants } from '../iconVariants'; + +@Component({ + selector: 'ods-office-icon', + standalone: true, + imports: [NgClass], + template: `<svg + [ngClass]="twMerge(iconVariants({ size }), 'fill-black', class)" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M2 21V4.75L7 1L12 4.75V7H22V21H2ZM4 19H6V17H4V19ZM4 15H6V13H4V15ZM4 11H6V9H4V11ZM4 7H6V5H4V7ZM8 7H10V5H8V7ZM8 19H20V9H8V19ZM14 13V11H18V13H14ZM14 17V15H18V17H14ZM10 13V11H12V13H10ZM10 17V15H12V17H10Z" + /> + </svg>`, +}) +export class OfficeIconComponent { + @Input() size: IconVariants['size'] = 'medium'; + @Input() class: string = undefined; + + iconVariants = iconVariants; + twMerge = twMerge; +} diff --git a/alfa-client/libs/design-system/src/lib/icons/office-icon/office-icon.stories.ts b/alfa-client/libs/design-system/src/lib/icons/office-icon/office-icon.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..8a54d90c40add3636c0f41c9748ab529f09fc253 --- /dev/null +++ b/alfa-client/libs/design-system/src/lib/icons/office-icon/office-icon.stories.ts @@ -0,0 +1,27 @@ +import type { Meta, StoryObj } from '@storybook/angular'; + +import { OfficeIconComponent } from './office-icon.component'; + +const meta: Meta<OfficeIconComponent> = { + title: 'Icons/Save icon', + component: OfficeIconComponent, + excludeStories: /.*Data$/, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj<OfficeIconComponent>; + +export const Default: Story = { + args: { size: 'large' }, + argTypes: { + size: { + control: 'select', + options: ['small', 'medium', 'large', 'extra-large', 'full'], + description: 'Size of icon. Property "full" means 100%', + table: { + defaultValue: { summary: 'medium' }, + }, + }, + }, +}; diff --git a/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.spec.ts b/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.spec.ts index f900a81ba73a568c8be41ba225930acfc7d687a0..72cc13e7708a00114b96193a07e83a855f0c628c 100644 --- a/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.spec.ts +++ b/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.spec.ts @@ -7,6 +7,7 @@ import { fakeAsync, tick, } from '@angular/core/testing'; +import { Resource } from '@ngxp/rest'; import { Subscription } from 'rxjs'; import { InstantSearchComponent } from './instant-search.component'; import { InstantSearchQuery, InstantSearchResult } from './instant-search.model'; @@ -15,7 +16,7 @@ describe('InstantSearchComponent', () => { let component: InstantSearchComponent; let fixture: ComponentFixture<InstantSearchComponent>; - const searchResults: InstantSearchResult<unknown>[] = [ + const searchResults: InstantSearchResult<Resource>[] = [ { title: 'test', description: 'test' }, { title: 'caption', description: 'desc' }, ]; @@ -185,7 +186,7 @@ describe('InstantSearchComponent', () => { describe('on null or undefined', () => { it.each([null, undefined])( 'should not call setSearchResults for %s', - (searchResults: InstantSearchResult<unknown>[]) => { + (searchResults: InstantSearchResult<Resource>[]) => { component.setSearchResults = jest.fn(); component.searchResults = searchResults; @@ -395,6 +396,14 @@ describe('InstantSearchComponent', () => { expect(component.areResultsVisible).toBe(false); }); + + it('should emit searchResultClosed event', () => { + component.searchResultClosed.emit = jest.fn(); + + component.hideResults(); + + expect(component.searchResultClosed.emit).toHaveBeenCalled(); + }); }); describe('onKeydownHandler', () => { diff --git a/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.ts b/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.ts index c16d9fab5189ca71bb978839e0b602d114d35882..b4e2dd528d8ec6f2b894b51802ec22581bd01f61 100644 --- a/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.ts +++ b/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.component.ts @@ -13,6 +13,7 @@ import { ViewChildren, } from '@angular/core'; import { FormControl } from '@angular/forms'; +import { Resource } from '@ngxp/rest'; import { isEqual, isUndefined } from 'lodash-es'; import { Subscription, debounceTime, distinctUntilChanged, filter } from 'rxjs'; import { AriaLiveRegionComponent } from '../../aria-live-region/aria-live-region.component'; @@ -41,6 +42,7 @@ import { InstantSearchQuery, InstantSearchResult } from './instant-search.model' [control]="control" aria-controls="results" (inputClicked)="showResults()" + (searchQueryCleared)="searchQueryCleared.emit()" #searchField /> <ods-aria-live-region [text]="ariaLiveText" /> @@ -49,7 +51,12 @@ import { InstantSearchQuery, InstantSearchResult } from './instant-search.model' class="absolute z-50 mt-3 w-full" id="results" > - <ods-search-result-header [text]="headerText" [count]="results.length" header /> + <ods-search-result-header + *ngIf="headerText" + [text]="headerText" + [count]="results.length" + header + /> <ods-search-result-item *ngFor="let result of results; let i = index" [title]="result.title" @@ -68,21 +75,23 @@ export class InstantSearchComponent implements OnInit, OnDestroy { @Input() headerText: string = EMPTY_STRING; @Input() control: FormControl<string> = new FormControl(EMPTY_STRING); - @Input() set searchResults(searchResults: InstantSearchResult<unknown>[]) { + @Input() set searchResults(searchResults: InstantSearchResult<Resource>[]) { if (!isEqual(searchResults, this.results) && isNotNil(searchResults)) { this.setSearchResults(searchResults); } } - @Output() searchResultSelected: EventEmitter<InstantSearchResult<unknown>> = new EventEmitter< - InstantSearchResult<unknown> + @Output() searchResultSelected: EventEmitter<InstantSearchResult<Resource>> = new EventEmitter< + InstantSearchResult<Resource> >(); + @Output() searchResultClosed: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>(); @Output() searchQueryChanged: EventEmitter<InstantSearchQuery> = new EventEmitter<InstantSearchQuery>(); + @Output() searchQueryCleared: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>(); readonly FIRST_ITEM_INDEX: number = 0; readonly PREVIEW_SEARCH_STRING_MIN_LENGTH: number = 2; - results: InstantSearchResult<unknown>[] = []; + results: InstantSearchResult<Resource>[] = []; ariaLiveText: string = ''; areResultsVisible: boolean = true; private focusedResult: number | undefined = undefined; @@ -145,7 +154,7 @@ export class InstantSearchComponent implements OnInit, OnDestroy { this.resultsRef.get(index).setFocus(); } - setSearchResults(searchResults: InstantSearchResult<unknown>[]): void { + setSearchResults(searchResults: InstantSearchResult<Resource>[]): void { this.results = searchResults; this.ariaLiveText = this.buildAriaLiveText(searchResults.length); } @@ -194,6 +203,7 @@ export class InstantSearchComponent implements OnInit, OnDestroy { hideResults(): void { this.areResultsVisible = false; this.focusedResult = undefined; + this.searchResultClosed.emit(); } isLastItemOrOutOfArray(index: number, arrayLength: number): boolean { @@ -216,7 +226,7 @@ export class InstantSearchComponent implements OnInit, OnDestroy { return e.key === 'Escape'; } - onItemClicked(searchResult: InstantSearchResult<unknown>, index: number) { + onItemClicked(searchResult: InstantSearchResult<Resource>, index: number) { this.searchResultSelected.emit(searchResult); this.focusedResult = index; this.hideResults(); diff --git a/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.model.ts b/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.model.ts index 5debae8ceb40be63d765fbb9a401cb47d601e5c5..ecf661f429fddde80b83340063ad95f54daba6b0 100644 --- a/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.model.ts +++ b/alfa-client/libs/design-system/src/lib/instant-search/instant-search/instant-search.model.ts @@ -1,4 +1,6 @@ -export interface InstantSearchResult<T> { +import { Resource } from '@ngxp/rest'; + +export interface InstantSearchResult<T extends Resource> { title: string; description: string; data?: T; diff --git a/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.component.spec.ts b/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.component.spec.ts index f1f730f80e1b6cf82b90568b16df0ffd8b3a76bd..81b3e439ee8eba573c9f17bf31a7a707d02dc911 100644 --- a/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.component.spec.ts +++ b/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.component.spec.ts @@ -43,5 +43,13 @@ describe('SearchFieldComponent', () => { expect(component.control.value).toBe(EMPTY_STRING); }); + + it('should emit searchQueryCleared event', () => { + component.searchQueryCleared.emit = jest.fn(); + + component.clearInput(); + + expect(component.searchQueryCleared.emit).toHaveBeenCalled(); + }); }); }); diff --git a/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.component.ts b/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.component.ts index 1f2e01bd046af2d15ce345fed69c9a9f43122cc6..2dc1f73fa4f735efa0d1fc6e18e2c4946ab383ef 100644 --- a/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.component.ts +++ b/alfa-client/libs/design-system/src/lib/instant-search/search-field/search-field.component.ts @@ -28,11 +28,13 @@ import { SearchIconComponent } from '../../icons/search-icon/search-icon.compone export class SearchFieldComponent { @Input() label: string = EMPTY_STRING; @Input() placeholder: string = EMPTY_STRING; - @Input() control = new FormControl(EMPTY_STRING); + @Input() control: FormControl<string> = new FormControl(EMPTY_STRING); @Output() inputClicked: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>(); + @Output() searchQueryCleared: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>(); - clearInput() { + clearInput(): void { this.control.setValue(EMPTY_STRING); + this.searchQueryCleared.emit(); } } diff --git a/alfa-client/libs/design-system/src/lib/tailwind-preset/root.css b/alfa-client/libs/design-system/src/lib/tailwind-preset/root.css index 7e997ffe4f9bfb19811dc99c99a708dd8d92e741..10ac4b918b2c3e41d10f5a1a0e41036a3cfdfc03 100644 --- a/alfa-client/libs/design-system/src/lib/tailwind-preset/root.css +++ b/alfa-client/libs/design-system/src/lib/tailwind-preset/root.css @@ -68,3 +68,7 @@ .bescheid-dialog-backdrop { @apply bg-gray-500 bg-opacity-30 transition-opacity; } + +.blur-dialog-backdrop { + @apply bg-gray-400 bg-opacity-75 backdrop-blur-sm transition-opacity dark:bg-gray-500 dark:bg-opacity-75; +} diff --git a/alfa-client/libs/design-system/src/lib/tailwind-preset/tailwind.config.js b/alfa-client/libs/design-system/src/lib/tailwind-preset/tailwind.config.js index 1c7b298d5afa17aae60ab57b36b9152cec514121..2fe29cc4a320a6a10b8dec75884f5f423b549e1d 100644 --- a/alfa-client/libs/design-system/src/lib/tailwind-preset/tailwind.config.js +++ b/alfa-client/libs/design-system/src/lib/tailwind-preset/tailwind.config.js @@ -100,6 +100,12 @@ module.exports = { error: 'hsl(var(--color-error))', focus: 'hsl(var(--color-focus))', }, + backgroundColor: { + greybackdrop: 'rgb(229, 229, 229, 0.95)', + }, + backdropBlur: { + 1: '1px', + }, }, }, plugins: [], diff --git a/alfa-client/libs/design-system/src/test/search.ts b/alfa-client/libs/design-system/src/test/search.ts new file mode 100644 index 0000000000000000000000000000000000000000..93b65de204918bfae7d639ba89d32f30e9c385f3 --- /dev/null +++ b/alfa-client/libs/design-system/src/test/search.ts @@ -0,0 +1,7 @@ +import { faker } from '@faker-js/faker'; +import { Resource } from '@ngxp/rest'; +import { InstantSearchResult } from '../lib/instant-search/instant-search/instant-search.model'; + +export function createInstantSearchResult<T extends Resource>(data?: T): InstantSearchResult<T> { + return { title: faker.random.word(), description: faker.random.words(3), data }; +} diff --git a/alfa-client/libs/tech-shared/src/index.ts b/alfa-client/libs/tech-shared/src/index.ts index 6447da0179658e13795f1dbe8bfa8f0df2adeda3..970130b8c3cc71fccd268eb842d34a249f578750 100644 --- a/alfa-client/libs/tech-shared/src/index.ts +++ b/alfa-client/libs/tech-shared/src/index.ts @@ -49,6 +49,7 @@ export * from './lib/pipe/to-traffic-light-tooltip.pipe'; export * from './lib/pipe/to-traffic-light.pipe'; export * from './lib/resource/api-resource.service'; export * from './lib/resource/list-resource.service'; +export * from './lib/resource/resource-search.service'; export * from './lib/resource/resource.model'; export * from './lib/resource/resource.repository'; export * from './lib/resource/resource.rxjs.operator'; diff --git a/alfa-client/libs/tech-shared/src/lib/pipe/convert-for-data-test.pipe.ts b/alfa-client/libs/tech-shared/src/lib/pipe/convert-for-data-test.pipe.ts index e056b77eb22f32ed6c2b9c9765c5101252acef18..cd455ac7098fe6f33d9883b87963e56900d2caec 100644 --- a/alfa-client/libs/tech-shared/src/lib/pipe/convert-for-data-test.pipe.ts +++ b/alfa-client/libs/tech-shared/src/lib/pipe/convert-for-data-test.pipe.ts @@ -27,7 +27,7 @@ import { convertForDataTest } from '../tech.util'; @Pipe({ name: 'convertForDataTest' }) export class ConvertForDataTestPipe implements PipeTransform { - transform(value: string) { + transform(value: string): string { return isNil(value) ? null : convertForDataTest(value); } } diff --git a/alfa-client/libs/tech-shared/src/lib/pipe/file-size-plain.pipe.ts b/alfa-client/libs/tech-shared/src/lib/pipe/file-size-plain.pipe.ts index 275302f1ae582e7b6060a4ecb23266cb2eea729e..43492c8fac7194f393a0fed2f69635372804d255 100644 --- a/alfa-client/libs/tech-shared/src/lib/pipe/file-size-plain.pipe.ts +++ b/alfa-client/libs/tech-shared/src/lib/pipe/file-size-plain.pipe.ts @@ -6,13 +6,13 @@ export class FileSizePlainPipe implements PipeTransform { readonly MB = Math.pow(this.kB, 2); readonly GB = Math.pow(this.kB, 3); - transform(size: number) { + transform(size: number): string { if (size >= this.GB) return this.formatFileSize(size / this.GB, 'GB'); if (size >= this.MB) return this.formatFileSize(size / this.MB, 'MB'); return this.formatFileSize(size / this.kB, 'kB'); } - private formatFileSize(number: number, unit: string) { + private formatFileSize(number: number, unit: string): string { return `${number.toFixed(2).replace('.', ',')} ${unit}`; } } diff --git a/alfa-client/libs/tech-shared/src/lib/pipe/file-size.pipe.ts b/alfa-client/libs/tech-shared/src/lib/pipe/file-size.pipe.ts index a8ca257d0b3687a5684b809694a56c421ae43ffd..31fb3d58dd0a46c21578be37e853e3b09997825e 100644 --- a/alfa-client/libs/tech-shared/src/lib/pipe/file-size.pipe.ts +++ b/alfa-client/libs/tech-shared/src/lib/pipe/file-size.pipe.ts @@ -29,13 +29,13 @@ export class FileSizePipe implements PipeTransform { readonly MB = Math.pow(this.kB, 2); readonly GB = Math.pow(this.kB, 3); - transform(size: number) { + transform(size: number): string { if (size >= this.GB) return this.formatFileSize(size / this.GB, 'GB'); if (size >= this.MB) return this.formatFileSize(size / this.MB, 'MB'); return this.formatFileSize(size / this.kB, 'kB'); } - private formatFileSize(number: number, unit: string) { + private formatFileSize(number: number, unit: string): string { return `${number.toFixed(2).replace('.', ',')}<span class="unit">${unit}</span>`; } } diff --git a/alfa-client/libs/tech-shared/src/lib/pipe/get-url.pipe.spec.ts b/alfa-client/libs/tech-shared/src/lib/pipe/get-url.pipe.spec.ts index d28782d0fef619b1cb5297d23e938957686b0b02..7387cf4d9b4d7279a9b8bddecedc0b48a670f6b7 100644 --- a/alfa-client/libs/tech-shared/src/lib/pipe/get-url.pipe.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/pipe/get-url.pipe.spec.ts @@ -1,5 +1,5 @@ import faker from '@faker-js/faker'; -import { Resource } from '@ngxp/rest'; +import { Resource, ResourceUri } from '@ngxp/rest'; import { GetUrlPipe } from './get-url.pipe'; describe('GetUrlPipe', () => { @@ -15,7 +15,7 @@ describe('GetUrlPipe', () => { const pipe: GetUrlPipe = new GetUrlPipe(); it('should return resource url', () => { - const result: string = pipe.transform(resource, selfLink); + const result: ResourceUri = pipe.transform(resource, selfLink); expect(result).toBe(url); }); diff --git a/alfa-client/libs/tech-shared/src/lib/pipe/get-url.pipe.ts b/alfa-client/libs/tech-shared/src/lib/pipe/get-url.pipe.ts index 0c130a8548597cfbcac2bf8d911b5bd1ee6ab175..32a4ee089847b5cd38365b6cd52683ba06b81203 100644 --- a/alfa-client/libs/tech-shared/src/lib/pipe/get-url.pipe.ts +++ b/alfa-client/libs/tech-shared/src/lib/pipe/get-url.pipe.ts @@ -1,9 +1,9 @@ import { Pipe, PipeTransform } from '@angular/core'; -import { Resource, getUrl } from '@ngxp/rest'; +import { Resource, ResourceUri, getUrl } from '@ngxp/rest'; @Pipe({ name: 'getUrl' }) export class GetUrlPipe implements PipeTransform { - transform(resource: Resource, link: string) { + transform(resource: Resource, link: string): ResourceUri { return getUrl(resource, link); } } diff --git a/alfa-client/libs/tech-shared/src/lib/pipe/has-link.pipe.ts b/alfa-client/libs/tech-shared/src/lib/pipe/has-link.pipe.ts index 70ac58beccf1b77fe59f7c50bd0b0fa2a0f6f254..311e6b39d2816155469a906027f76784881a5155 100644 --- a/alfa-client/libs/tech-shared/src/lib/pipe/has-link.pipe.ts +++ b/alfa-client/libs/tech-shared/src/lib/pipe/has-link.pipe.ts @@ -22,11 +22,11 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { Pipe, PipeTransform } from '@angular/core'; -import { hasLink, Resource } from '@ngxp/rest'; +import { Resource, hasLink } from '@ngxp/rest'; @Pipe({ name: 'hasLink' }) export class HasLinkPipe implements PipeTransform { - transform(resource: Resource, link: string) { + transform(resource: Resource, link: string): boolean { return hasLink(resource, link); } } diff --git a/alfa-client/libs/tech-shared/src/lib/pipe/not-has-link.pipe.ts b/alfa-client/libs/tech-shared/src/lib/pipe/not-has-link.pipe.ts index 8f8f786c562cf744e412b91f62a66827897535c7..e7bb7f28d2e877e4918e6119103238b839c05e56 100644 --- a/alfa-client/libs/tech-shared/src/lib/pipe/not-has-link.pipe.ts +++ b/alfa-client/libs/tech-shared/src/lib/pipe/not-has-link.pipe.ts @@ -22,11 +22,11 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { Pipe, PipeTransform } from '@angular/core'; -import { hasLink, Resource } from '@ngxp/rest'; +import { Resource, hasLink } from '@ngxp/rest'; @Pipe({ name: 'notHasLink' }) export class NotHasLinkPipe implements PipeTransform { - transform(resource: Resource, link: string) { + transform(resource: Resource, link: string): boolean { return !hasLink(resource, link); } } diff --git a/alfa-client/libs/tech-shared/src/lib/pipe/to-embedded-resource.pipe.spec.ts b/alfa-client/libs/tech-shared/src/lib/pipe/to-embedded-resource.pipe.spec.ts index 309a04197539defcfa29b8320840d4f84aab7721..a4873730938d80aa0efe333f7177aca4e2e4ff28 100644 --- a/alfa-client/libs/tech-shared/src/lib/pipe/to-embedded-resource.pipe.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/pipe/to-embedded-resource.pipe.spec.ts @@ -24,34 +24,36 @@ import { getEmbeddedResource } from '@ngxp/rest'; import { DummyListLinkRel } from 'libs/tech-shared/test/dummy'; import { createDummyListResource } from 'libs/tech-shared/test/resource'; +import { ListResource } from '../resource/resource.util'; +import { EMPTY_ARRAY } from '../tech.util'; import { ToEmbeddedResourcesPipe } from './to-embedded-resource.pipe'; describe('ToEmbeddedResourcesPipe', () => { - const pipe = new ToEmbeddedResourcesPipe(); + const pipe: ToEmbeddedResourcesPipe = new ToEmbeddedResourcesPipe(); it('create an instance', () => { expect(pipe).toBeTruthy(); }); describe('transform listResource to resources by link', () => { - const listResource = createDummyListResource([DummyListLinkRel.LIST]); + const listResource: ListResource = createDummyListResource([DummyListLinkRel.LIST]); it('should return embedded resources', () => { - const result = pipe.transform(listResource, DummyListLinkRel.LIST); + const result: unknown[] = pipe.transform(listResource, DummyListLinkRel.LIST); expect(result).toBe(getEmbeddedResource(listResource, DummyListLinkRel.LIST)); }); - it('should return null by empty listResource', () => { - const result = pipe.transform(null, DummyListLinkRel.LIST); + it('should return an empty array on null as listResource', () => { + const result: unknown[] = pipe.transform(null, DummyListLinkRel.LIST); - expect(result).toBe(null); + expect(result).toBe(EMPTY_ARRAY); }); - it('should return null by empty linkel', () => { - const result = pipe.transform(listResource, null); + it('should return empty array on null as linkel', () => { + const result: unknown[] = pipe.transform(listResource, null); - expect(result).toBe(null); + expect(result).toBe(EMPTY_ARRAY); }); }); }); diff --git a/alfa-client/libs/tech-shared/src/lib/pipe/to-embedded-resource.pipe.ts b/alfa-client/libs/tech-shared/src/lib/pipe/to-embedded-resource.pipe.ts index 53aec73b8461648669ba4f95b7d4aa1ecb3c5021..77e51945183b3b392c2b3d49647af0bf4a0e02ea 100644 --- a/alfa-client/libs/tech-shared/src/lib/pipe/to-embedded-resource.pipe.ts +++ b/alfa-client/libs/tech-shared/src/lib/pipe/to-embedded-resource.pipe.ts @@ -22,13 +22,16 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { Pipe, PipeTransform } from '@angular/core'; -import { getEmbeddedResource } from '@ngxp/rest'; +import { Resource, getEmbeddedResource } from '@ngxp/rest'; import { isNil } from 'lodash-es'; +import { LinkRelationName } from '../resource/resource.model'; +import { ListResource } from '../resource/resource.util'; +import { EMPTY_ARRAY } from '../tech.util'; @Pipe({ name: 'toEmbeddedResources' }) export class ToEmbeddedResourcesPipe implements PipeTransform { - transform(listResource: any, linkRel: any) { - if (isNil(listResource) || isNil(linkRel)) return null; + transform(listResource: ListResource, linkRel: LinkRelationName): Resource[] { + if (isNil(listResource) || isNil(linkRel)) return EMPTY_ARRAY; return getEmbeddedResource(listResource, linkRel); } } diff --git a/alfa-client/libs/tech-shared/src/lib/pipe/to-resource-uri.pipe.spec.ts b/alfa-client/libs/tech-shared/src/lib/pipe/to-resource-uri.pipe.spec.ts index 6f709453705f390368bf511ccef753555b246769..389a82c47e1526c02c5464eb41cddc507d86c49e 100644 --- a/alfa-client/libs/tech-shared/src/lib/pipe/to-resource-uri.pipe.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/pipe/to-resource-uri.pipe.spec.ts @@ -21,7 +21,7 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -import { getUrl, Resource } from '@ngxp/rest'; +import { getUrl, Resource, ResourceUri } from '@ngxp/rest'; import { createDummyResource } from 'libs/tech-shared/test/resource'; import { decodeUrlFromEmbedding } from '../tech.util'; import { ToResourceUriPipe } from './to-resource-uri.pipe'; @@ -38,14 +38,14 @@ describe('ToResourceUriPipe', () => { const dummyResource: Resource = createDummyResource(); it('output as expected', () => { - const pipeResult: string = pipe.transform(dummyResource); + const pipeResult: ResourceUri = pipe.transform(dummyResource); const result: string = decodeUrlFromEmbedding(pipeResult); expect(result).toBe(getUrl(dummyResource)); }); it('replace "/" with "_" if necessary', () => { - const pipeResult: string = pipe.transform({ + const pipeResult: ResourceUri = pipe.transform({ _links: { self: { href: base64ResultWithSlash } }, }); @@ -60,7 +60,7 @@ describe('ToResourceUriPipe', () => { const dummyResourceWithLink: Resource = createDummyResource([linkRel]); it('output as expected', () => { - const pipeResult: string = pipe.transform(dummyResourceWithLink, linkRel); + const pipeResult: ResourceUri = pipe.transform(dummyResourceWithLink, linkRel); const result: string = decodeUrlFromEmbedding(pipeResult); diff --git a/alfa-client/libs/tech-shared/src/lib/pipe/to-resource-uri.pipe.ts b/alfa-client/libs/tech-shared/src/lib/pipe/to-resource-uri.pipe.ts index 4c1d7971cd774eb1b53fc0b6bda21744f1a5235b..30d6f4c7e466d6f7e43075af5bc7f74a534c6557 100644 --- a/alfa-client/libs/tech-shared/src/lib/pipe/to-resource-uri.pipe.ts +++ b/alfa-client/libs/tech-shared/src/lib/pipe/to-resource-uri.pipe.ts @@ -22,13 +22,13 @@ * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { Pipe, PipeTransform } from '@angular/core'; -import { Resource } from '@ngxp/rest'; +import { Resource, ResourceUri } from '@ngxp/rest'; import { isNil } from 'lodash-es'; import { toResourceUri } from '../resource/resource.util'; @Pipe({ name: 'toResourceUri' }) export class ToResourceUriPipe implements PipeTransform { - transform(resource: Resource, linkRel?: string): string { + transform(resource: Resource, linkRel?: string): ResourceUri { if (isNil(resource)) { return null; } diff --git a/alfa-client/libs/tech-shared/src/lib/resource/resource-search.service.spec.ts b/alfa-client/libs/tech-shared/src/lib/resource/resource-search.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..cec2eb392cca4a78a1ae77660048157d00169deb --- /dev/null +++ b/alfa-client/libs/tech-shared/src/lib/resource/resource-search.service.spec.ts @@ -0,0 +1,228 @@ +import { Mock, mock, useFromMock } from '@alfa-client/test-utils'; +import { fakeAsync, tick } from '@angular/core/testing'; +import faker from '@faker-js/faker'; +import { Resource, getUrl } from '@ngxp/rest'; +import { DummyLinkRel } from 'libs/tech-shared/test/dummy'; +import { createDummyListResource, createDummyResource } from 'libs/tech-shared/test/resource'; +import { BehaviorSubject, Observable, of } from 'rxjs'; +import { singleColdCompleted } from '../../../test/marbles'; +import { EMPTY_STRING } from '../tech.util'; +import { ResourceSearchService } from './resource-search.service'; +import { LinkRelationName, ListItemResource, SearchResourceServiceConfig } from './resource.model'; +import { ResourceRepository } from './resource.repository'; +import { + ListResource, + StateResource, + createEmptyStateResource, + createStateResource, +} from './resource.util'; + +describe('ResourceSearchService', () => { + let service: ResourceSearchService<Resource, ListResource, ListItemResource>; + let config: SearchResourceServiceConfig<Resource>; + let repository: Mock<ResourceRepository>; + + const baseResource: Resource = createDummyResource(); + const baseResourceSubj: BehaviorSubject<Resource> = new BehaviorSubject<Resource>(baseResource); + const searchLinkRel: LinkRelationName = DummyLinkRel.DUMMY; + + const listResource: ListResource = createDummyListResource(); + const stateListResource: StateResource<ListResource> = createStateResource(listResource); + + const searchBy: string = faker.random.words(2); + + beforeEach(() => { + config = { + baseResource: baseResourceSubj, + searchLinkRel, + }; + repository = mock(ResourceRepository); + + service = new ResourceSearchService(config, useFromMock(repository)); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + describe('get result list', () => { + beforeEach(() => { + service.handleChanges = jest.fn(); + + service.listResource.next(stateListResource); + service.searchBy.next(searchBy); + }); + + it('should call handleChanges', fakeAsync(() => { + service.getResultList().subscribe(); + tick(); + + expect(service.handleChanges).toHaveBeenCalledWith(stateListResource, searchBy, baseResource); + })); + + it('should return value', (done) => { + service.getResultList().subscribe((resultList) => { + expect(resultList).toBe(stateListResource); + done(); + }); + }); + }); + + describe('handle changes', () => { + it('should call doSearch on loading flag', () => { + service.doSearch = jest.fn(); + + service.handleChanges({ ...stateListResource, loading: true }, searchBy, baseResource); + + expect(service.doSearch).toHaveBeenCalledWith(searchBy, baseResource); + }); + + it('should NOT call doSearch on loading flag false', () => { + service.doSearch = jest.fn(); + + service.handleChanges({ ...stateListResource, loading: false }, searchBy, baseResource); + + expect(service.doSearch).not.toHaveBeenCalled(); + }); + }); + + describe('do search', () => { + beforeEach(() => { + service.dispatchSearch = jest.fn(); + }); + describe('on existing searchBy', () => { + it('should call dispatchSearch', () => { + service.doSearch(searchBy, baseResource); + + expect(service.dispatchSearch).toHaveBeenCalledWith(baseResource, searchLinkRel, searchBy); + }); + + it('should NOT clear list resource', () => { + service.listResource.next(stateListResource); + + service.doSearch(searchBy, baseResource); + + expect(service.listResource.value).toBe(stateListResource); + }); + }); + + describe('on empty searchBy', () => { + it('should clear list resource', () => { + service.listResource.next(stateListResource); + + service.doSearch(EMPTY_STRING, baseResource); + + expect(service.listResource.value).toEqual(createEmptyStateResource()); + }); + + it('should NOT call dispatchSearch', () => { + service.doSearch(EMPTY_STRING, baseResource); + + expect(service.dispatchSearch).not.toHaveBeenCalled(); + }); + }); + }); + + describe('dispatch search', () => { + beforeEach(() => { + repository.search.mockReturnValue(of(listResource)); + }); + + it('should call respository', () => { + service.dispatchSearch(baseResource, searchLinkRel, searchBy); + + expect(repository.search).toHaveBeenCalledWith(baseResource, searchLinkRel, searchBy); + }); + + it('should update list resource', () => { + service.listResource.next(createEmptyStateResource()); + + service.dispatchSearch(baseResource, searchLinkRel, searchBy); + + expect(service.listResource.value).toEqual(createStateResource(listResource)); + }); + }); + + describe('clear search list', () => { + it('should call dispatchClearSearch listResource by given result', () => { + service.listResource.next(stateListResource); + + service.clearResultList(); + + expect(service.listResource.value).toEqual(createEmptyStateResource()); + }); + }); + + describe('saerch', () => { + it('should set searchBy', () => { + service.search(searchBy); + + expect(service.searchBy.value).toEqual(searchBy); + }); + + it('should set list resource loading', () => { + service.listResource.next(stateListResource); + + service.search(searchBy); + + expect(service.listResource.value).toEqual({ ...stateListResource, loading: true }); + }); + }); + + describe('get selected', () => { + const dummyStateResource: StateResource<Resource> = createStateResource(createDummyResource()); + + it('should return selected resource', () => { + service.getSelectedResult = jest.fn().mockReturnValue(of(dummyStateResource)); + const selectedResult$: Observable<StateResource<Resource>> = service.getSelectedResult(); + + expect(selectedResult$).toBeObservable(singleColdCompleted(dummyStateResource)); + }); + }); + + describe('select result', () => { + const dummyResource: Resource = createDummyResource(); + + beforeEach(() => { + service.setSelectedResourceLoading = jest.fn(); + repository.getResource.mockReturnValue(of(dummyResource)); + }); + + it('should call setSelectedResourceLoading', () => { + service.selectResult(dummyResource); + + expect(service.setSelectedResourceLoading).toHaveBeenCalled(); + }); + + it('should call repository', () => { + service.selectResult(dummyResource); + + expect(repository.getResource).toHaveBeenCalledWith(getUrl(dummyResource)); + }); + + it('should update selected resource', () => { + service.selectedResource.next(createEmptyStateResource()); + + service.selectResult(dummyResource); + + expect(service.selectedResource.value).toEqual(createStateResource(dummyResource)); + }); + }); + + describe('set selected resource loading', () => { + const selectedDummyResource: Resource = createDummyResource(); + const selectedDummyStateResource: StateResource<Resource> = + createStateResource(selectedDummyResource); + + it('should set loading flag to true', () => { + service.selectedResource.next(selectedDummyStateResource); + + service.setSelectedResourceLoading(); + + expect(service.selectedResource.value).toEqual({ + ...selectedDummyStateResource, + loading: true, + }); + }); + }); +}); diff --git a/alfa-client/libs/tech-shared/src/lib/resource/resource-search.service.ts b/alfa-client/libs/tech-shared/src/lib/resource/resource-search.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..c21f6e99a23e4392ef858d4ef5bd4e655a894fae --- /dev/null +++ b/alfa-client/libs/tech-shared/src/lib/resource/resource-search.service.ts @@ -0,0 +1,120 @@ +import { Resource, getUrl } from '@ngxp/rest'; +import { isEmpty } from 'lodash-es'; +import { BehaviorSubject, Observable, first, map, tap, withLatestFrom } from 'rxjs'; +import { EMPTY_STRING, isNotEmpty } from '../tech.util'; +import { LinkRelationName, ListItemResource, SearchResourceServiceConfig } from './resource.model'; +import { ResourceRepository } from './resource.repository'; +import { + ListResource, + StateResource, + createEmptyStateResource, + createStateResource, +} from './resource.util'; + +/** + * B = Type of baseresource + * T = Type of listresource + * I = Type of items in listresource + */ +export class ResourceSearchService< + B extends Resource, + T extends ListResource, + I extends ListItemResource, +> { + readonly listResource: BehaviorSubject<StateResource<T>> = new BehaviorSubject( + createEmptyStateResource(), + ); + readonly searchBy: BehaviorSubject<string> = new BehaviorSubject<string>(EMPTY_STRING); + readonly selectedResource: BehaviorSubject<StateResource<I>> = new BehaviorSubject< + StateResource<I> + >(createEmptyStateResource()); + + constructor( + private config: SearchResourceServiceConfig<B>, + private repository: ResourceRepository, + ) {} + + public getResultList(): Observable<StateResource<T>> { + return this.selectListResource().pipe( + withLatestFrom(this.selectSearchBy(), this.config.baseResource), + tap(([listResource, searchBy, baseResource]) => { + this.handleChanges(listResource, searchBy, baseResource); + }), + map(([listResource]) => listResource), + ); + } + + private selectListResource(): Observable<StateResource<T>> { + return this.listResource.asObservable(); + } + + private selectSearchBy(): Observable<string> { + return this.searchBy.asObservable(); + } + + handleChanges(listResource: StateResource<T>, searchBy: string, baseResource: B): void { + if (listResource.loading) this.doSearch(searchBy, baseResource); + } + + doSearch(searchBy: string, baseResource: B): void { + if (isNotEmpty(searchBy)) { + this.dispatchSearch(baseResource, this.config.searchLinkRel, searchBy); + } + if (isEmpty(searchBy)) { + this.dispatchClearSearchList(); + } + } + + dispatchSearch(baseResource: B, linkRel: LinkRelationName, searchBy: string): void { + this.repository + .search<T>(baseResource, linkRel, searchBy) + .pipe(first()) + .subscribe((result: T) => this.dispatchSearchSuccess(result)); + } + + private dispatchSearchSuccess(result: T): void { + this.listResource.next(createStateResource(result)); + } + + public clearResultList(): void { + this.dispatchClearSearchList(); + } + + private dispatchClearSearchList(): void { + this.listResource.next(createEmptyStateResource()); + } + + public search(searchBy: string): void { + this.dispatchInitSearch(searchBy); + } + + private dispatchInitSearch(searchBy: string): void { + this.searchBy.next(searchBy); + this.listResource.next({ ...this.listResource.value, loading: true }); + } + + public getSelectedResult(): Observable<StateResource<I>> { + return this.selectedResource.asObservable(); + } + + public selectResult(selected: I): void { + this.dispatchInitSelectResult(selected); + } + + private dispatchInitSelectResult(selected: I) { + this.setSelectedResourceLoading(); + this.repository + .getResource(getUrl(selected)) + .pipe(first()) + .subscribe((result: I) => { + this.selectedResource.next(createStateResource(result)); + }); + } + + setSelectedResourceLoading(): void { + this.selectedResource.next({ + ...this.selectedResource.value, + loading: true, + }); + } +} diff --git a/alfa-client/libs/tech-shared/src/lib/resource/resource.model.ts b/alfa-client/libs/tech-shared/src/lib/resource/resource.model.ts index 5fa5264f32b11a057af8755641f61d02b001fc46..bbc1b169e432eadfe036aea7e0446a2ba1840910 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/resource.model.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/resource.model.ts @@ -30,3 +30,8 @@ export interface ResourceServiceConfig<B> { delete?: { linkRel: LinkRelationName; order?: string }; edit?: { linkRel: LinkRelationName; order?: string }; } + +export interface SearchResourceServiceConfig<B> { + baseResource: Observable<B>; + searchLinkRel: LinkRelationName; +} diff --git a/alfa-client/libs/tech-shared/src/lib/resource/resource.repository.spec.ts b/alfa-client/libs/tech-shared/src/lib/resource/resource.repository.spec.ts index d9b2b8cd5662996a5c8eb0129e54779ee289a105..2c8c97bfd4304f4bad5545a11cbe97dcb8a7258f 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/resource.repository.spec.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/resource.repository.spec.ts @@ -203,4 +203,62 @@ describe('ResourceRepository', () => { expect(result).toBeObservable(singleHot(deletedResource)); }); }); + + describe('search', () => { + const linkRel: string = DummyLinkRel.DUMMY; + const dummyResource: Resource = createDummyResource([linkRel]); + const searchBy: string = faker.random.word(); + + const searchUrl: string = faker.internet.url(); + + const dummyListResource: ListResource = createDummyListResource(); + + it('should call buildSearchUri', () => { + repository.buildSearchUri = jest.fn(); + + repository.search(dummyResource, linkRel, searchBy); + + expect(repository.buildSearchUri).toHaveBeenCalledWith( + new URL(getUrl(dummyResource, linkRel)), + searchBy, + ); + }); + + it('should call resourceFactory', () => { + repository.buildSearchUri = jest.fn().mockReturnValue(searchUrl); + + repository.search(dummyResource, linkRel, searchBy); + + expect(resourceFactory.fromId).toHaveBeenCalledWith(searchUrl); + }); + + it('should call resourceWrapper', () => { + repository.buildSearchUri = jest.fn().mockReturnValue(searchUrl); + + repository.search(dummyResource, linkRel, searchBy); + + expect(resourceWrapper.get).toHaveBeenCalled(); + }); + + it('should return result', () => { + repository.buildSearchUri = jest.fn().mockReturnValue(searchUrl); + resourceWrapper.get.mockReturnValue(singleCold(dummyListResource)); + + const result: Observable<Resource> = repository.search(dummyResource, linkRel, searchBy); + + expect(result).not.toBeNull(); + expect(result).toBeObservable(singleHot(dummyListResource)); + }); + + describe('build search uri', () => { + const url: URL = new URL('http://test/searchit?searchBy={searchBy}'); + const searchBy: string = faker.random.word(); + + it('should return uri contains searchBy param', () => { + const searchUri: ResourceUri = repository.buildSearchUri(url, searchBy); + + expect(searchUri).toEqual(`http://test/searchit?searchBy=${searchBy}`); + }); + }); + }); }); diff --git a/alfa-client/libs/tech-shared/src/lib/resource/resource.repository.ts b/alfa-client/libs/tech-shared/src/lib/resource/resource.repository.ts index 386819910fd1aa3b9fd58349e7a9034f72530982..b0fcb3995f0961983b8caef53160762ad3fc4416 100644 --- a/alfa-client/libs/tech-shared/src/lib/resource/resource.repository.ts +++ b/alfa-client/libs/tech-shared/src/lib/resource/resource.repository.ts @@ -6,6 +6,8 @@ import { ListResource } from './resource.util'; @Injectable({ providedIn: 'root' }) export class ResourceRepository { + static SEARCH_PARAM: string = 'searchBy'; + constructor(private resourceFactory: ResourceFactory) {} public getListResource(resource: Resource, linkRel: string): Observable<ListResource> { @@ -39,4 +41,15 @@ export class ResourceRepository { public delete(resource: Resource, linkRel: LinkRelationName): Observable<Resource> { return this.resourceFactory.from(resource).delete(linkRel); } + + public search<T>(resource: Resource, linkRel: LinkRelationName, searchBy: string): Observable<T> { + return this.resourceFactory + .fromId(this.buildSearchUri(new URL(getUrl(resource, linkRel)), searchBy)) + .get(); + } + + buildSearchUri(url: URL, searchBy: string): ResourceUri { + url.searchParams.set(ResourceRepository.SEARCH_PARAM, searchBy); + return url.href; + } } diff --git a/alfa-client/libs/test-utils/src/index.ts b/alfa-client/libs/test-utils/src/index.ts index 2496647d81ac7e86f904aef5e70b2031539d4541..9500ab27220d6d74064d30a8d163c00f020ce2f7 100644 --- a/alfa-client/libs/test-utils/src/index.ts +++ b/alfa-client/libs/test-utils/src/index.ts @@ -21,7 +21,9 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ +export * from './lib/dialog'; export * from './lib/helper'; export * from './lib/jest.helper'; export * from './lib/mocking'; +export * from './lib/model'; export * from './lib/test-utils.module'; diff --git a/alfa-client/libs/test-utils/src/lib/dialog.ts b/alfa-client/libs/test-utils/src/lib/dialog.ts new file mode 100644 index 0000000000000000000000000000000000000000..d9a32f03a17d554f48e5263d1df4c4b7bb552b9b --- /dev/null +++ b/alfa-client/libs/test-utils/src/lib/dialog.ts @@ -0,0 +1 @@ +export const dialogRefMock = { close: jest.fn() }; diff --git a/alfa-client/libs/test-utils/src/lib/helper.ts b/alfa-client/libs/test-utils/src/lib/helper.ts index ed3e6fa137f9399a1ce428b627c13b47a70947f9..b95ce2e05c2a1912d502259ff1172ae34b16959b 100644 --- a/alfa-client/libs/test-utils/src/lib/helper.ts +++ b/alfa-client/libs/test-utils/src/lib/helper.ts @@ -24,6 +24,7 @@ import { DebugElement, Type } from '@angular/core'; import { ComponentFixture } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; +import { EventData } from './model'; export function getElementFromFixtureByType<T>( fixture: ComponentFixture<any>, @@ -47,8 +48,8 @@ export function getElementsFromFixture(fixture: ComponentFixture<any>, htmlEleme return fixture.nativeElement.querySelectorAll(htmlElement); } -export function dispatchEventFromFixture( - fixture: ComponentFixture<any>, +export function dispatchEventFromFixture<T>( + fixture: ComponentFixture<T>, elementSelector: string, event: string, ): void { @@ -56,6 +57,14 @@ export function dispatchEventFromFixture( element.nativeElement.dispatchEvent(new Event(event)); } +export function triggerEvent<T>(eventData: EventData<T>) { + const element: DebugElement = getDebugElementFromFixtureByCss( + eventData.fixture, + eventData.elementSelector, + ); + element.triggerEventHandler(eventData.name, eventData.data); +} + export function getDebugElementFromFixtureByCss( fixture: ComponentFixture<any>, query: string, diff --git a/alfa-client/libs/test-utils/src/lib/model.ts b/alfa-client/libs/test-utils/src/lib/model.ts new file mode 100644 index 0000000000000000000000000000000000000000..e48d4ed88c7c2447665811a86fc712dc2a4904da --- /dev/null +++ b/alfa-client/libs/test-utils/src/lib/model.ts @@ -0,0 +1,8 @@ +import { ComponentFixture } from '@angular/core/testing'; + +export interface EventData<T> { + fixture: ComponentFixture<T>; + elementSelector: string; + name: string; + data?: unknown; +} diff --git a/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.service.spec.ts b/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.service.spec.ts index 90ba85e40ec86b4c785793fecc5e5e6b0d4e850e..8fac9311bbcd78efbdd449922e9901f6cd97aa45 100644 --- a/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.service.spec.ts +++ b/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.service.spec.ts @@ -10,12 +10,20 @@ describe('OzgcloudDialogService', () => { const component = <any>{ name: 'Component' }; const dialogData = { id: 'ZumBeispiel' }; + const dialogConfig: DialogConfig = { + hasBackdrop: true, + disableClose: true, + }; const dialogConfigWithData: DialogConfig = { data: dialogData }; const viewContainerRef = <any>{}; const dialogConfigWithDataAndViewContainerRef: DialogConfig = { data: dialogData, viewContainerRef, }; + const dialogConfigWithOwnConfigAndViewContainerRef: DialogConfig = { + ...dialogConfig, + viewContainerRef, + }; const dialogConfigWithOutDataAndWithViewContainerRef: DialogConfig = { viewContainerRef, }; @@ -59,7 +67,16 @@ describe('OzgcloudDialogService', () => { expect(dialog.open).toHaveBeenCalledWith(component, dialogConfigWithDataAndViewContainerRef); }); - it('should open dialog wihtout data', () => { + it('should open dialog with custom config', () => { + service.openInCallingComponentContext(component, viewContainerRef, null, dialogConfig); + + expect(dialog.open).toHaveBeenCalledWith( + component, + dialogConfigWithOwnConfigAndViewContainerRef, + ); + }); + + it('should open dialog without data and custom config', () => { service.openInCallingComponentContext(component, viewContainerRef); expect(dialog.open).toHaveBeenCalledWith( diff --git a/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.service.ts b/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.service.ts index 27769a03b7ebd03c1db3a8ad50ee02d5b5ce9a69..3c9383b4a60f1293b42ee00b3acc3fa90a54abaf 100644 --- a/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.service.ts +++ b/alfa-client/libs/ui/src/lib/ui/ozgcloud-dialog/ozgcloud-dialog.service.ts @@ -30,8 +30,12 @@ export class OzgcloudDialogService { component: ComponentType<T>, viewContainerRef: ViewContainerRef, data?: D, + dialogConfig?: DialogConfig, ): DialogRef<T> { - return this.openDialog(component, this.buildDialogConfigWithData(data, { viewContainerRef })); + return this.openDialog( + component, + this.buildDialogConfigWithData(data, { viewContainerRef, ...dialogConfig }), + ); } private buildDialogConfigWithData<D>(data: D, dialogConfig?: DialogConfig): DialogConfig | null { diff --git a/alfa-client/libs/vorgang-shared/src/lib/vorgang.linkrel.ts b/alfa-client/libs/vorgang-shared/src/lib/vorgang.linkrel.ts index 21dc4295af4576f3d4e3439ada7935dcd45a5275..a10af56d72cac1ad7b7d9ce522d924cce6ee4d09 100644 --- a/alfa-client/libs/vorgang-shared/src/lib/vorgang.linkrel.ts +++ b/alfa-client/libs/vorgang-shared/src/lib/vorgang.linkrel.ts @@ -66,6 +66,7 @@ export enum VorgangWithEingangLinkRel { DOWNLOAD_ATTACHMENTS = 'downloadAttachments', CREATE_COLLABORATION_REQUEST = 'createCollaborationRequest', + SEARCH_ORGANISATIONS_EINHEIT = 'searchOrganisationsEinheit', } export enum LoeschAnforderungLinkRel { diff --git a/alfa-server/src/main/resources/application.yml b/alfa-server/src/main/resources/application.yml index ac930de36a83c9b7d962e72dca5ef29eae2fdd57..0f0c8e24b2ea7023c57f11e22b8f3d7c997864e3 100644 --- a/alfa-server/src/main/resources/application.yml +++ b/alfa-server/src/main/resources/application.yml @@ -61,6 +61,9 @@ grpc: user-manager: address: static://127.0.0.1:9000 negotiationType: TLS + zufi-manager: + address: static://127.0.0.1:9190 + negotiationType: TLS ozgcloud: auth: diff --git a/alfa-service/pom.xml b/alfa-service/pom.xml index 4d4e6c9ae8b0eca82e0af3bf544e12e11dc7fa15..9f540119c2fdada17280122da064557d1fc1078a 100644 --- a/alfa-service/pom.xml +++ b/alfa-service/pom.xml @@ -24,7 +24,9 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> @@ -127,6 +129,10 @@ <groupId>de.ozgcloud.user</groupId> <artifactId>user-manager-interface</artifactId> </dependency> + <dependency> + <groupId>de.ozgcloud.zufi</groupId> + <artifactId>zufi-manager-interface</artifactId> + </dependency> <!-- tools --> <dependency> @@ -229,4 +235,4 @@ </plugins> </build> -</project> +</project> \ No newline at end of file diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/Anschrift.java b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/Anschrift.java new file mode 100644 index 0000000000000000000000000000000000000000..e39bea0b531da3fa6d82b4a122f226135714d96f --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/Anschrift.java @@ -0,0 +1,15 @@ +package de.ozgcloud.alfa.collaboration; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +class Anschrift { + + private String strasse; + private String hausnummer; + private String plz; + private String ort; + +} \ No newline at end of file diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessor.java b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessor.java index d8c0a04560d014f582b19b2aeecf5306d2ed39be..a8b01feec3ac1c8e1ece0e0e089fc7acd22f07df 100644 --- a/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessor.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessor.java @@ -23,6 +23,7 @@ import lombok.RequiredArgsConstructor; class CollaborationVorgangProcessor implements RepresentationModelProcessor<EntityModel<VorgangWithEingang>> { static final LinkRelation REL_CREATE_COLLABORATION_REQUEST = LinkRelation.of("createCollaborationRequest"); + static final LinkRelation REL_SEARCH_ORGANISATIONS_EINHEIT = LinkRelation.of("searchOrganisationsEinheit"); private final CurrentUserService currentUserService; @@ -30,14 +31,15 @@ class CollaborationVorgangProcessor implements RepresentationModelProcessor<Enti public EntityModel<VorgangWithEingang> process(EntityModel<VorgangWithEingang> model) { var vorgang = model.getContent(); - if (Objects.isNull(vorgang)) { + if (Objects.isNull(vorgang) || !currentUserService.hasRole(UserRole.VERWALTUNG_USER)) { return model; } return ModelBuilder.fromModel(model) - .ifMatch(() -> currentUserService.hasRole(UserRole.VERWALTUNG_USER)) - .addLink(linkTo(methodOn(CommandController.CommandByRelationController.class).createCommand(vorgang.getId(), vorgang.getId(), - vorgang.getVersion(), null)).withRel(REL_CREATE_COLLABORATION_REQUEST)) + .addLinks( + linkTo(methodOn(CommandController.CommandByRelationController.class).createCommand(vorgang.getId(), vorgang.getId(), + vorgang.getVersion(), null)).withRel(REL_CREATE_COLLABORATION_REQUEST), + linkTo(methodOn(OrganisationsEinheitController.class).getAllBySearchBy(null)).withRel(REL_SEARCH_ORGANISATIONS_EINHEIT)) .buildModel(); } } diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheit.java b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheit.java new file mode 100644 index 0000000000000000000000000000000000000000..8b1deacb31d69aa7de439d3ef5112e68dfd6ada2 --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheit.java @@ -0,0 +1,13 @@ +package de.ozgcloud.alfa.collaboration; + +import lombok.Builder; +import lombok.Getter; +import lombok.extern.jackson.Jacksonized; + +@Builder +@Getter +@Jacksonized +class OrganisationsEinheit { + + private String id; +} diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitController.java b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitController.java new file mode 100644 index 0000000000000000000000000000000000000000..301528e79ac9d1058a54bd31fd611d83104d0eac --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitController.java @@ -0,0 +1,37 @@ +package de.ozgcloud.alfa.collaboration; + +import java.util.Optional; + +import org.springframework.hateoas.CollectionModel; +import org.springframework.hateoas.EntityModel; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping(OrganisationsEinheitController.PATH) +@RequiredArgsConstructor +class OrganisationsEinheitController { + + static final String PATH = "/api/organisationseinheits"; // NOSONAR + static final String SEARCH_BY_PARAM = "searchBy"; + + private final OrganisationsEinheitService service; + private final OrganisationsEinheitModelAssembler assembler; + private final OrganisationsEinheitHeaderModelAssembler headerModelAssembler; + + @GetMapping("/{organisationsEinheitId}") + public ResponseEntity<EntityModel<OrganisationsEinheit>> getById(@PathVariable String organisationsEinheitId) { + return ResponseEntity.of(Optional.of(service.getById(organisationsEinheitId)).map(assembler::toModel)); + } + + @GetMapping(params = { SEARCH_BY_PARAM }) + public CollectionModel<EntityModel<OrganisationsEinheitHeader>> getAllBySearchBy(@RequestParam String searchBy) { + return headerModelAssembler.toCollectionModel(service.searchOrganisationsEinheiten(searchBy).toList()); + } +} diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeader.java b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeader.java new file mode 100644 index 0000000000000000000000000000000000000000..ebe24c6a9a962df8c6438ee52da962871f630254 --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeader.java @@ -0,0 +1,17 @@ +package de.ozgcloud.alfa.collaboration; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +class OrganisationsEinheitHeader { + + @JsonIgnore + private String id; + private String name; + private Anschrift anschrift; + +} diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeaderMapper.java b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeaderMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..0fa1bb19f6546781d3cc0b3ea4a9b111c09f7be2 --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeaderMapper.java @@ -0,0 +1,11 @@ +package de.ozgcloud.alfa.collaboration; + +import org.mapstruct.Mapper; + +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheit; + +@Mapper +interface OrganisationsEinheitHeaderMapper { + + OrganisationsEinheitHeader fromGrpc(GrpcOrganisationsEinheit organisationsEinheit); +} diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeaderModelAssembler.java b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeaderModelAssembler.java new file mode 100644 index 0000000000000000000000000000000000000000..8cbea84ba7535ce790c4dc8d80333f9d47737319 --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeaderModelAssembler.java @@ -0,0 +1,30 @@ +package de.ozgcloud.alfa.collaboration; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*; + +import jakarta.annotation.Nonnull; + +import org.springframework.hateoas.CollectionModel; +import org.springframework.hateoas.EntityModel; +import org.springframework.hateoas.server.RepresentationModelAssembler; +import org.springframework.stereotype.Component; + +import de.ozgcloud.alfa.common.ModelBuilder; + +@Component +class OrganisationsEinheitHeaderModelAssembler + implements RepresentationModelAssembler<OrganisationsEinheitHeader, EntityModel<OrganisationsEinheitHeader>> { + + @Override + public EntityModel<OrganisationsEinheitHeader> toModel(@Nonnull OrganisationsEinheitHeader entity) { + return ModelBuilder.fromEntity(entity) + .addLink(linkTo(OrganisationsEinheitController.class).slash(entity.getId()).withSelfRel()) + .buildModel(); + } + + @Override + public CollectionModel<EntityModel<OrganisationsEinheitHeader>> toCollectionModel(Iterable<? extends OrganisationsEinheitHeader> entities) { + return RepresentationModelAssembler.super.toCollectionModel(entities) + .add(linkTo(OrganisationsEinheitController.class).withSelfRel()); + } +} diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitMapper.java b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..20c30041b9642e66551eaecc771091bec74d7920 --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitMapper.java @@ -0,0 +1,11 @@ +package de.ozgcloud.alfa.collaboration; + +import org.mapstruct.Mapper; + +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheit; + +@Mapper +interface OrganisationsEinheitMapper { + + OrganisationsEinheit fromGrpc(GrpcOrganisationsEinheit organisationsEinheit); +} diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitModelAssembler.java b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitModelAssembler.java new file mode 100644 index 0000000000000000000000000000000000000000..60f582dfdb3477e72aecd9ad3bf3062d37838fdc --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitModelAssembler.java @@ -0,0 +1,20 @@ +package de.ozgcloud.alfa.collaboration; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*; + +import org.springframework.hateoas.EntityModel; +import org.springframework.hateoas.server.RepresentationModelAssembler; +import org.springframework.stereotype.Component; + +import de.ozgcloud.alfa.common.ModelBuilder; + +@Component +class OrganisationsEinheitModelAssembler implements RepresentationModelAssembler<OrganisationsEinheit, EntityModel<OrganisationsEinheit>> { + + @Override + public EntityModel<OrganisationsEinheit> toModel(OrganisationsEinheit entity) { + return ModelBuilder.fromEntity(entity) + .addLink(linkTo(OrganisationsEinheitController.class).slash(entity.getId()).withSelfRel()) + .buildModel(); + } +} diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitRemoteService.java b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitRemoteService.java new file mode 100644 index 0000000000000000000000000000000000000000..aeea7b92941b95fab767bb1641920739e78b8092 --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitRemoteService.java @@ -0,0 +1,48 @@ +package de.ozgcloud.alfa.collaboration; + +import java.util.stream.Stream; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import de.ozgcloud.alfa.common.GrpcUtil; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheitGetRequest; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheitGetResponse; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheitSearchRequest; +import de.ozgcloud.zufi.grpc.organisationseinheit.OrganisationsEinheitServiceGrpc.OrganisationsEinheitServiceBlockingStub; +import net.devh.boot.grpc.client.inject.GrpcClient; + +@Service +class OrganisationsEinheitRemoteService { + + @GrpcClient(GrpcUtil.ZUFI_MANAGER_GRPC_CLIENT) + private OrganisationsEinheitServiceBlockingStub serviceStub; + + @Autowired + private OrganisationsEinheitHeaderMapper organisationsEinheitHeaderMapper; + @Autowired + private OrganisationsEinheitMapper organisationsEinheitMapper; + + public Stream<OrganisationsEinheitHeader> search(String searchBy) { + var response = serviceStub.search(buildSearchRequest(searchBy)); + return response.getOrganisationsEinheitenList().stream().map(organisationsEinheitHeaderMapper::fromGrpc); + } + + private GrpcOrganisationsEinheitSearchRequest buildSearchRequest(String searchBy) { + return GrpcOrganisationsEinheitSearchRequest.newBuilder().setSearchBy(searchBy).build(); + } + + public OrganisationsEinheit getById(String id) { + var request = buildGetByIdRequest(id); + var response = serviceStub.getById(request); + return getOrganisationsEinheitFromGetByIdResponse(response); + } + + GrpcOrganisationsEinheitGetRequest buildGetByIdRequest(String organisationsEinheitId) { + return GrpcOrganisationsEinheitGetRequest.newBuilder().setId(organisationsEinheitId).build(); + } + + OrganisationsEinheit getOrganisationsEinheitFromGetByIdResponse(GrpcOrganisationsEinheitGetResponse response) { + return organisationsEinheitMapper.fromGrpc(response.getOrganisationsEinheit()); + } +} diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitService.java b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitService.java new file mode 100644 index 0000000000000000000000000000000000000000..58afb0f569cf5353de916024011e9670f3a9c537 --- /dev/null +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitService.java @@ -0,0 +1,23 @@ +package de.ozgcloud.alfa.collaboration; + +import java.util.stream.Stream; + +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +class OrganisationsEinheitService { + + private final OrganisationsEinheitRemoteService remoteService; + + public OrganisationsEinheit getById(String id) { + return remoteService.getById(id); + } + + public Stream<OrganisationsEinheitHeader> searchOrganisationsEinheiten(String searchBy) { + return remoteService.search(searchBy); + } + +} diff --git a/alfa-service/src/main/java/de/ozgcloud/alfa/common/GrpcUtil.java b/alfa-service/src/main/java/de/ozgcloud/alfa/common/GrpcUtil.java index d11d5297b8373ad3d84900333a7f996c7e2db650..fc033f658d18ce1a5726b631e32ab7b591c5817f 100644 --- a/alfa-service/src/main/java/de/ozgcloud/alfa/common/GrpcUtil.java +++ b/alfa-service/src/main/java/de/ozgcloud/alfa/common/GrpcUtil.java @@ -38,6 +38,8 @@ public class GrpcUtil { public static final String VORGANG_MANAGER_GRPC_CLIENT = "vorgang-manager"; + public static final String ZUFI_MANAGER_GRPC_CLIENT = "zufi-manager"; + public static final String SERVICE_KEY = "GRPC_SERVICE"; public static Key<String> keyOfString(String key) { diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/bescheid/BescheidVorgangProcessorTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/bescheid/BescheidVorgangProcessorTest.java index fc714d41d1e45acb791372084e94c4c0b0bddab8..1b897c7d005dadfed9e1a76145eb873061b31e1b 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/bescheid/BescheidVorgangProcessorTest.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/bescheid/BescheidVorgangProcessorTest.java @@ -20,6 +20,7 @@ import org.mockito.Spy; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.Link; +import de.ozgcloud.alfa.common.EntityModelTestFactory; import de.ozgcloud.alfa.common.UserProfileUrlProvider; import de.ozgcloud.alfa.common.user.CurrentUserService; import de.ozgcloud.alfa.common.user.UserRole; @@ -27,7 +28,6 @@ import de.ozgcloud.alfa.vorgang.Vorgang; import de.ozgcloud.alfa.vorgang.VorgangHeaderTestFactory; import de.ozgcloud.alfa.vorgang.VorgangWithEingang; import de.ozgcloud.alfa.vorgang.VorgangWithEingangTestFactory; -import lombok.NoArgsConstructor; class BescheidVorgangProcessorTest { @@ -47,11 +47,9 @@ class BescheidVorgangProcessorTest { @Test void shouldReturnTheSameModelOnNullVorgang() { - var inputModel = new NullableEntityModel(); + var processedModel = processor.process(EntityModelTestFactory.NULLABLE); - var processedModel = processor.process(inputModel); - - assertThat(processedModel).isEqualTo(inputModel); + assertThat(processedModel).isEqualTo(EntityModelTestFactory.NULLABLE); } @Test @@ -175,11 +173,6 @@ class BescheidVorgangProcessorTest { private EntityModel<VorgangWithEingang> callProcess() { return processor.process(EntityModel.of(vorgang)); } - - @NoArgsConstructor - private static class NullableEntityModel extends EntityModel<VorgangWithEingang> { - - } } @DisplayName("Exists bescheid") diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/AnschriftTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/AnschriftTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..6036b9c1544846dd762deffc756f5ef6ac94b456 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/AnschriftTestFactory.java @@ -0,0 +1,24 @@ +package de.ozgcloud.alfa.collaboration; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.alfa.collaboration.Anschrift.AnschriftBuilder; + +public class AnschriftTestFactory { + public static final String PLZ = LoremIpsum.getInstance().getZipCode(); + public static final String ORT = LoremIpsum.getInstance().getCity(); + public static final String STRASSE = LoremIpsum.getInstance().getWords(2); + public static final String HAUSNUMMER = String.valueOf((int) (Math.random() * 1000)); + + public static Anschrift create() { + return createBuilder().build(); + } + + public static AnschriftBuilder createBuilder() { + return Anschrift.builder() + .strasse(STRASSE) + .ort(ORT) + .hausnummer(HAUSNUMMER) + .plz(PLZ); + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessorTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessorTest.java index 5a51e828cc7b1eec1fbe93d2125722ee038e3954..f298e909253839bb53643caced64f281f6c554c1 100644 --- a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessorTest.java +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/CollaborationVorgangProcessorTest.java @@ -4,7 +4,6 @@ import static de.ozgcloud.alfa.common.UserProfileUrlProviderTestFactory.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; @@ -14,11 +13,13 @@ import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.Link; import org.springframework.hateoas.UriTemplate; +import de.ozgcloud.alfa.common.EntityModelTestFactory; import de.ozgcloud.alfa.common.UserProfileUrlProvider; import de.ozgcloud.alfa.common.command.CommandController; import de.ozgcloud.alfa.common.user.CurrentUserService; import de.ozgcloud.alfa.common.user.UserRole; import de.ozgcloud.alfa.vorgang.VorgangHeaderTestFactory; +import de.ozgcloud.alfa.vorgang.VorgangWithEingang; import de.ozgcloud.alfa.vorgang.VorgangWithEingangTestFactory; class CollaborationVorgangProcessorTest { @@ -35,46 +36,96 @@ class CollaborationVorgangProcessorTest { @Nested class TestProcess { + @Test + void shouldNotAddLinksIfVorgangIsNull() { + var model = processor.process(EntityModelTestFactory.NULLABLE); + + assertThat(model.hasLinks()).isFalse(); + } + @Nested - class OnNullVorgang { + class LinkToCreateCollaborationRequest { @Test - void shouldNotAddLinksIfVorgangIsNull() { - var model = processor.process(new EntityModel<>() { - }); + void shouldAddLink() { + initUserProfileUrlProvider(urlProvider); + givenUserHasRoleVerwaltungUser(); + + var model = callProcessor(); - assertThat(model.hasLinks()).isFalse(); + assertThat(model.getLink(CollaborationVorgangProcessor.REL_CREATE_COLLABORATION_REQUEST)).isPresent(); + } + + @Test + void shouldHaveHref() { + initUserProfileUrlProvider(urlProvider); + givenUserHasRoleVerwaltungUser(); + + var model = callProcessor(); + + assertThat(model.getLink(CollaborationVorgangProcessor.REL_CREATE_COLLABORATION_REQUEST)).get() + .extracting(Link::getHref) + .isEqualTo(UriTemplate.of(CommandController.CommandByRelationController.COMMAND_BY_RELATION_PATH) + .expand(VorgangHeaderTestFactory.ID, VorgangHeaderTestFactory.ID, VorgangHeaderTestFactory.VERSION).toString()); + } + + @Test + void shouldNotAddLinkIfUserHasNoRole() { + givenUserDoesNotHaveRoleVerwaltungUser(); + + var model = callProcessor(); + + assertThat(model.getLink(CollaborationVorgangProcessor.REL_CREATE_COLLABORATION_REQUEST)).isEmpty(); } } @Nested - class OnNonNullVorgang { + class LinkToSearchOrganisationsEinheit { - @BeforeEach - void prepareBuilder() { + @Test + void shouldAddLink() { initUserProfileUrlProvider(urlProvider); + givenUserHasRoleVerwaltungUser(); + + var model = callProcessor(); + + assertThat(model.getLink(CollaborationVorgangProcessor.REL_SEARCH_ORGANISATIONS_EINHEIT)).isPresent(); } @Test - void shouldAddCreateCollaborationRequestRelation() { - when(currentUserService.hasRole(UserRole.VERWALTUNG_USER)).thenReturn(true); + void shouldHaveHref() { + initUserProfileUrlProvider(urlProvider); + givenUserHasRoleVerwaltungUser(); - var model = processor.process(EntityModel.of(VorgangWithEingangTestFactory.create())); + var model = callProcessor(); - assertThat(model.getLink(CollaborationVorgangProcessor.REL_CREATE_COLLABORATION_REQUEST)).isPresent().get() + assertThat(model.getLink(CollaborationVorgangProcessor.REL_SEARCH_ORGANISATIONS_EINHEIT)).get() .extracting(Link::getHref) - .isEqualTo(UriTemplate.of(CommandController.CommandByRelationController.COMMAND_BY_RELATION_PATH) - .expand(VorgangHeaderTestFactory.ID, VorgangHeaderTestFactory.ID, VorgangHeaderTestFactory.VERSION).toString()); + .isEqualTo( + String.format("%s?%s={%s}", OrganisationsEinheitController.PATH, OrganisationsEinheitController.SEARCH_BY_PARAM, + OrganisationsEinheitController.SEARCH_BY_PARAM)); } @Test - void shouldNotAddCreateCollaborationRequestRelation() { - when(currentUserService.hasRole(UserRole.VERWALTUNG_USER)).thenReturn(false); + void shouldNotAddLinkIfUserHasNoRole() { + givenUserDoesNotHaveRoleVerwaltungUser(); - var model = processor.process(EntityModel.of(VorgangWithEingangTestFactory.create())); + var model = callProcessor(); - assertThat(model.getLink(CollaborationVorgangProcessor.REL_CREATE_COLLABORATION_REQUEST)).isEmpty(); + assertThat(model.getLink(CollaborationVorgangProcessor.REL_SEARCH_ORGANISATIONS_EINHEIT)).isEmpty(); } } + + private void givenUserHasRoleVerwaltungUser() { + when(currentUserService.hasRole(UserRole.VERWALTUNG_USER)).thenReturn(true); + } + + private void givenUserDoesNotHaveRoleVerwaltungUser() { + when(currentUserService.hasRole(UserRole.VERWALTUNG_USER)).thenReturn(false); + } + + private EntityModel<VorgangWithEingang> callProcessor() { + return processor.process(EntityModel.of(VorgangWithEingangTestFactory.create())); + } } } diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcAnschriftTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcAnschriftTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..8f06c7fdd95ab06bae9054f94a72a1cd45f6a47b --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcAnschriftTestFactory.java @@ -0,0 +1,24 @@ +package de.ozgcloud.alfa.collaboration; + +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcAnschrift; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcAnschrift.Builder; + +public class GrpcAnschriftTestFactory { + public static final String STRASSE = AnschriftTestFactory.STRASSE; + public static final String HAUSNUMMER = AnschriftTestFactory.HAUSNUMMER; + public static final String PLZ = AnschriftTestFactory.PLZ; + public static final String ORT = AnschriftTestFactory.ORT; + + public static GrpcAnschrift create() { + return createBuilder().build(); + } + + public static Builder createBuilder() { + return GrpcAnschrift.newBuilder() + .setStrasse(STRASSE) + .setHausnummer(HAUSNUMMER) + .setPlz(PLZ) + .setOrt(ORT); + } + +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcOrganisationsEinheitGetRequestTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcOrganisationsEinheitGetRequestTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..518acd646c25f50be9cdffffb43e412b8ccc4bea --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcOrganisationsEinheitGetRequestTestFactory.java @@ -0,0 +1,19 @@ +package de.ozgcloud.alfa.collaboration; + +import java.util.UUID; + +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheitGetRequest; + +class GrpcOrganisationsEinheitGetRequestTestFactory { + + public static final String ID = UUID.randomUUID().toString(); + + public static GrpcOrganisationsEinheitGetRequest create() { + return createBuilder().build(); + } + + public static GrpcOrganisationsEinheitGetRequest.Builder createBuilder() { + return GrpcOrganisationsEinheitGetRequest.newBuilder().setId(ID); + } + +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcOrganisationsEinheitGetResponseTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcOrganisationsEinheitGetResponseTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..a1c5a313d04f5d631b84b13579b84cbbdd71fedd --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcOrganisationsEinheitGetResponseTestFactory.java @@ -0,0 +1,18 @@ +package de.ozgcloud.alfa.collaboration; + +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheit; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheitGetResponse; + +class GrpcOrganisationsEinheitGetResponseTestFactory { + + public static final GrpcOrganisationsEinheit GRPC_ORGANISATIONS_EINHEIT = GrpcOrganisationsEinheitTestFactory.create(); + + public static GrpcOrganisationsEinheitGetResponse create() { + return createBuilder().build(); + } + + public static GrpcOrganisationsEinheitGetResponse.Builder createBuilder() { + return GrpcOrganisationsEinheitGetResponse.newBuilder().setOrganisationsEinheit(GRPC_ORGANISATIONS_EINHEIT); + } + +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcOrganisationsEinheitSearchResponseTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcOrganisationsEinheitSearchResponseTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..34d6204cb5a1ea202f1fa9b7565f40d27b12ab12 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcOrganisationsEinheitSearchResponseTestFactory.java @@ -0,0 +1,20 @@ +package de.ozgcloud.alfa.collaboration; + +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheit; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheitSearchResponse; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheitSearchResponse.Builder; + +public class GrpcOrganisationsEinheitSearchResponseTestFactory { + + public static final GrpcOrganisationsEinheit ORGANISATIONS_EINHEIT = GrpcOrganisationsEinheitTestFactory.create(); + + public static GrpcOrganisationsEinheitSearchResponse create() { + return createBuilder() + .build(); + } + + public static Builder createBuilder() { + return GrpcOrganisationsEinheitSearchResponse.newBuilder() + .addOrganisationsEinheiten(ORGANISATIONS_EINHEIT); + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcOrganisationsEinheitTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcOrganisationsEinheitTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..7cb95a9e54f601044af15c30c06e783fdf50a09e --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcOrganisationsEinheitTestFactory.java @@ -0,0 +1,44 @@ +package de.ozgcloud.alfa.collaboration; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcAnschrift; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheit; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheit.Builder; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcXzufiId; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcZustaendigkeit; + +public class GrpcOrganisationsEinheitTestFactory { + public static final String ID = OrganisationsEinheitHeaderTestFactory.ID; + public static final GrpcXzufiId XZUFI_ID = GrpcXzufiIdTestFactory.create(); + public static final String NAME = OrganisationsEinheitHeaderTestFactory.NAME; + public static final String SYNONYME = LoremIpsum.getInstance().getWords(5); + public static final String VORGANG_MANAGER_ADDRESS = LoremIpsum.getInstance().getUrl(); + public static final GrpcAnschrift ANSCHRIFT = GrpcAnschriftTestFactory.create(); + public static final GrpcZustaendigkeit ZUSTAENDIGKEIT = GrpcZustaendigkeitTestFactory.create(); + + public static GrpcOrganisationsEinheit create() { + return createBuilder().build(); + } + + public static GrpcOrganisationsEinheit createWithoutSynonyme() { + return createBuilderWithoutSynonyme() + .build(); + } + + public static Builder createBuilder() { + return createBuilderWithoutSynonyme() + .setSynonyme(SYNONYME); + } + + public static Builder createBuilderWithoutSynonyme() { + return GrpcOrganisationsEinheit.newBuilder() + .setId(ID) + .setName(NAME) + .setAnschrift(ANSCHRIFT) + .setVorgangManagerAddress(VORGANG_MANAGER_ADDRESS) + .addZustaendigkeiten(ZUSTAENDIGKEIT) + .setXzufiId(XZUFI_ID); + } + +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcXzufiIdTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcXzufiIdTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..5ed243a1e9fc69b34c7947487c98af3061094153 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcXzufiIdTestFactory.java @@ -0,0 +1,23 @@ +package de.ozgcloud.alfa.collaboration; + +import java.util.UUID; + +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcXzufiId; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcXzufiId.Builder; + +public class GrpcXzufiIdTestFactory { + public static final String ORGANISATIONS_EINHEIT_ID = UUID.randomUUID().toString(); + public static final String SCHEME_AGENCY_ID = UUID.randomUUID().toString(); + + public static GrpcXzufiId create() { + return createBuilder(ORGANISATIONS_EINHEIT_ID, SCHEME_AGENCY_ID).build(); + } + + public static GrpcXzufiId create(String id, String schemeAgencyId) { + return createBuilder(id, schemeAgencyId).build(); + } + + public static Builder createBuilder(String id, String schemeAgencyId) { + return GrpcXzufiId.newBuilder().setId(id).setSchemeAgencyId(schemeAgencyId); + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcZustaendigkeitTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcZustaendigkeitTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..f3d14432adde69cdbf395ed58bc988befdbd0238 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/GrpcZustaendigkeitTestFactory.java @@ -0,0 +1,22 @@ +package de.ozgcloud.alfa.collaboration; + +import java.util.UUID; + +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcXzufiId; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcZustaendigkeit; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcZustaendigkeit.Builder; + +public class GrpcZustaendigkeitTestFactory { + public static final GrpcXzufiId ID = GrpcXzufiIdTestFactory.create(); + public static final String GEBIET_ID = UUID.randomUUID().toString(); + + public static GrpcZustaendigkeit create() { + return createBuilder().build(); + } + + private static Builder createBuilder() { + return GrpcZustaendigkeit.newBuilder() + .setXzufiId(ID) + .setGebietId(GEBIET_ID); + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitControllerTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..aad546fdba57c6e25e5638b54fa8b6b33a72769b --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitControllerTest.java @@ -0,0 +1,216 @@ +package de.ozgcloud.alfa.collaboration; + +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.hateoas.CollectionModel; +import org.springframework.hateoas.EntityModel; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import com.thedeanda.lorem.LoremIpsum; + +import lombok.SneakyThrows; + +class OrganisationsEinheitControllerTest { + + @InjectMocks + private OrganisationsEinheitController controller; + + @Mock + private OrganisationsEinheitService service; + + @Mock + private OrganisationsEinheitModelAssembler assembler; + + @Mock + private OrganisationsEinheitHeaderModelAssembler headerModelAssembler; + + private MockMvc mockMvc; + + @BeforeEach + void initTest() { + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + + @Nested + class TestGetById { + + private final OrganisationsEinheit organisationsEinheit = OrganisationsEinheitTestFactory.create(); + + @BeforeEach + void mockService() { + when(service.getById(OrganisationsEinheitTestFactory.ID)).thenReturn(organisationsEinheit); + } + + @SneakyThrows + @Test + void shouldCallOrganisationsEinheitService() { + performRequest(); + + verify(service).getById(OrganisationsEinheitTestFactory.ID); + } + + @Test + void shouldCallAssembler() { + performRequest(); + + verify(assembler).toModel(organisationsEinheit); + } + + @SneakyThrows + @Test + void shouldReturnStatusOk() { + when(assembler.toModel(organisationsEinheit)).thenReturn(EntityModel.of(organisationsEinheit)); + + var response = performRequest(); + + response.andExpect(status().isOk()); + } + + @SneakyThrows + @Test + void shouldHaveOrganisationsEinheitId() { + when(assembler.toModel(organisationsEinheit)).thenReturn(EntityModel.of(organisationsEinheit)); + + var response = performRequest(); + + response.andExpect(jsonPath("$.id").value(OrganisationsEinheitTestFactory.ID)); + } + + @SneakyThrows + private ResultActions performRequest() { + return mockMvc.perform(get(OrganisationsEinheitController.PATH + "/" + OrganisationsEinheitTestFactory.ID)); + } + } + + @Nested + class TestGetAllBySearchBy { + + private final OrganisationsEinheitHeader organisationsEinheitHeader1 = OrganisationsEinheitHeaderTestFactory.create(); + private final OrganisationsEinheitHeader organisationsEinheitHeader2 = OrganisationsEinheitHeaderTestFactory.createBuilder() + .name(LoremIpsum.getInstance().getName()) + .build(); + private final String searchBy = LoremIpsum.getInstance().getWords(5); + private final List<OrganisationsEinheitHeader> organisationsEinheitHeaders = List.of(organisationsEinheitHeader1, + organisationsEinheitHeader2); + + @BeforeEach + void setUpMocks() { + when(service.searchOrganisationsEinheiten(searchBy)).thenReturn(Stream.of(organisationsEinheitHeader1, organisationsEinheitHeader2)); + when(headerModelAssembler.toCollectionModel(organisationsEinheitHeaders)) + .thenReturn( + CollectionModel.of(List.of(EntityModel.of(organisationsEinheitHeader1), EntityModel.of(organisationsEinheitHeader2)))); + } + + @Test + void shouldCallService() { + performRequest(); + + verify(service).searchOrganisationsEinheiten(searchBy); + } + + @Test + void shouldCallModelAssembler() { + performRequest(); + + verify(headerModelAssembler).toCollectionModel(organisationsEinheitHeaders); + } + + @SneakyThrows + @Test + void shouldReturnStatusOk() { + var response = performRequest(); + + response.andExpect(status().isOk()); + } + + @Test + @SneakyThrows + void shouldHaveFirstOrganisationsEinheitName() { + var response = performRequest(); + response.andExpect(jsonPath("$.content[0].name").value(organisationsEinheitHeader1.getName())); + } + + @Test + @SneakyThrows + void shouldHaveFirstOrganisationsEinheitStrasse() { + var response = performRequest(); + response.andExpect(jsonPath("$.content[0].anschrift.strasse").value(organisationsEinheitHeader1.getAnschrift().getStrasse())); + } + + @Test + @SneakyThrows + void shouldHaveFirstOrganisationsEinheitHausnummer() { + var response = performRequest(); + response.andExpect(jsonPath("$.content[0].anschrift.hausnummer").value(organisationsEinheitHeader1.getAnschrift().getHausnummer())); + } + + @Test + @SneakyThrows + void shouldHaveFirstOrganisationsEinheitPlz() { + var response = performRequest(); + response.andExpect(jsonPath("$.content[0].anschrift.plz").value(organisationsEinheitHeader1.getAnschrift().getPlz())); + } + + @Test + @SneakyThrows + void shouldHaveFirstOrganisationsEinheitOrt() { + var response = performRequest(); + response.andExpect(jsonPath("$.content[0].anschrift.ort").value(organisationsEinheitHeader1.getAnschrift().getOrt())); + } + + @Test + @SneakyThrows + void shouldHaveSecondOrganisationsEinheitName() { + var response = performRequest(); + response.andExpect(jsonPath("$.content[1].name").value(organisationsEinheitHeader2.getName())); + } + + @Test + @SneakyThrows + void shouldHaveSecondOrganisationsEinheitStrasse() { + var response = performRequest(); + response.andExpect(jsonPath("$.content[1].anschrift.strasse").value(organisationsEinheitHeader2.getAnschrift().getStrasse())); + } + + @Test + @SneakyThrows + void shouldHaveSecondOrganisationsEinheitHausnummer() { + var response = performRequest(); + response.andExpect(jsonPath("$.content[1].anschrift.hausnummer").value(organisationsEinheitHeader2.getAnschrift().getHausnummer())); + } + + @Test + @SneakyThrows + void shouldHaveSecondOrganisationsEinheitPlz() { + var response = performRequest(); + response.andExpect(jsonPath("$.content[1].anschrift.plz").value(organisationsEinheitHeader2.getAnschrift().getPlz())); + } + + @Test + @SneakyThrows + void shouldHaveSecondOrganisationsEinheitOrt() { + var response = performRequest(); + response.andExpect(jsonPath("$.content[1].anschrift.ort").value(organisationsEinheitHeader2.getAnschrift().getOrt())); + } + + @SneakyThrows + private ResultActions performRequest() { + return mockMvc.perform(get(OrganisationsEinheitController.PATH) + .param("searchBy", searchBy)).andExpect(status().isOk()); + + } + } + +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeaderMapperTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeaderMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a259f026c0a25df609a79cff423c8e51b0f3a339 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeaderMapperTest.java @@ -0,0 +1,23 @@ +package de.ozgcloud.alfa.collaboration; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; + +class OrganisationsEinheitHeaderMapperTest { + + private final OrganisationsEinheitHeaderMapper mapper = Mappers.getMapper(OrganisationsEinheitHeaderMapper.class); + + @Nested + class TestFromGrpc { + + @Test + void shouldMap() { + var organisationsEinheitHeader = mapper.fromGrpc(GrpcOrganisationsEinheitTestFactory.create()); + + assertThat(organisationsEinheitHeader).usingRecursiveComparison().isEqualTo(OrganisationsEinheitHeaderTestFactory.create()); + } + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeaderModelAssemblerTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeaderModelAssemblerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a876719e5ea939789c054347b8bdc878115a301b --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeaderModelAssemblerTest.java @@ -0,0 +1,71 @@ +package de.ozgcloud.alfa.collaboration; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.List; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Spy; +import org.springframework.hateoas.CollectionModel; +import org.springframework.hateoas.EntityModel; +import org.springframework.hateoas.IanaLinkRelations; +import org.springframework.hateoas.Link; + +class OrganisationsEinheitHeaderModelAssemblerTest { + + @Spy + private OrganisationsEinheitHeaderModelAssembler assembler; + + @Nested + class TestToModel { + + @Test + void shouldHaveSelfLink() { + var model = assembler.toModel(OrganisationsEinheitHeaderTestFactory.create()); + + assertHaveSelfLink(model); + } + + private void assertHaveSelfLink(EntityModel<OrganisationsEinheitHeader> model) { + assertThat(model.getLink(IanaLinkRelations.SELF_VALUE)).isPresent().get().extracting(Link::getHref) + .isEqualTo(OrganisationsEinheitController.PATH + "/" + OrganisationsEinheitHeaderTestFactory.ID); + } + } + + @Nested + class TestToCollectionModel { + + private OrganisationsEinheitHeader organisationsEinheitHeader = OrganisationsEinheitHeaderTestFactory.create(); + + @Test + void shouldCallToModel() { + callAssembler(); + + verify(assembler).toModel(organisationsEinheitHeader); + } + + @Test + void shouldHaveSelfLink() { + var model = callAssembler(); + + assertThat(model.getLink(IanaLinkRelations.SELF_VALUE)).isPresent().get().extracting(Link::getHref) + .isEqualTo(OrganisationsEinheitController.PATH); + } + + @Test + void shouldContainEntityModel() { + var entityModel = EntityModel.of(organisationsEinheitHeader); + doReturn(entityModel).when(assembler).toModel(organisationsEinheitHeader); + + var model = callAssembler(); + + assertThat(model).containsExactly(entityModel); + } + + private CollectionModel<EntityModel<OrganisationsEinheitHeader>> callAssembler() { + return assembler.toCollectionModel(List.of(organisationsEinheitHeader)); + } + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeaderTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeaderTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..4eef705ebdb94c9d108aab2312d070d6aa63f2ce --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitHeaderTestFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.alfa.collaboration; + +import java.util.UUID; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.alfa.collaboration.OrganisationsEinheitHeader.OrganisationsEinheitHeaderBuilder;; + +public class OrganisationsEinheitHeaderTestFactory { + public static final String ID = UUID.randomUUID().toString(); + public static final String NAME = LoremIpsum.getInstance().getName(); + public static final Anschrift ANSCHRIFT = AnschriftTestFactory.create(); + + public static OrganisationsEinheitHeader create() { + return createBuilder().build(); + } + + public static OrganisationsEinheitHeaderBuilder createBuilder() { + return OrganisationsEinheitHeader.builder() + .id(ID) + .name(NAME) + .anschrift(ANSCHRIFT); + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitMapperTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..1dcce17665e45327791fdecbe19ea3b4eaf4be01 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitMapperTest.java @@ -0,0 +1,23 @@ +package de.ozgcloud.alfa.collaboration; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; + +class OrganisationsEinheitMapperTest { + + private final OrganisationsEinheitMapper mapper = Mappers.getMapper(OrganisationsEinheitMapper.class); + + @Nested + class TestFromGrpc { + + @Test + void shouldMap() { + var organisationsEinheit = mapper.fromGrpc(GrpcOrganisationsEinheitTestFactory.create()); + + assertThat(organisationsEinheit).usingRecursiveComparison().isEqualTo(OrganisationsEinheitTestFactory.create()); + } + } +} \ No newline at end of file diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitModelAssemblerTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitModelAssemblerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8f51eeb7262a0f8f8e62bb91a264d8561efaa7e8 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitModelAssemblerTest.java @@ -0,0 +1,27 @@ +package de.ozgcloud.alfa.collaboration; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Spy; +import org.springframework.hateoas.IanaLinkRelations; +import org.springframework.hateoas.Link; + +class OrganisationsEinheitModelAssemblerTest { + + @Spy + private OrganisationsEinheitModelAssembler assembler; + + @Nested + class TestToModel { + + @Test + void shouldHaveSelfLink() { + var model = assembler.toModel(OrganisationsEinheitTestFactory.create()); + + assertThat(model.getLink(IanaLinkRelations.SELF_VALUE)).isPresent().get().extracting(Link::getHref) + .isEqualTo(OrganisationsEinheitController.PATH + "/" + OrganisationsEinheitTestFactory.ID); + } + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitRemoteServiceTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitRemoteServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b8c151afaa513c5026966501071c4719c60d21fe --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitRemoteServiceTest.java @@ -0,0 +1,172 @@ +package de.ozgcloud.alfa.collaboration; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; + +import com.thedeanda.lorem.LoremIpsum; + +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheitGetRequest; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheitGetResponse; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheitSearchRequest; +import de.ozgcloud.zufi.grpc.organisationseinheit.GrpcOrganisationsEinheitSearchResponse; +import de.ozgcloud.zufi.grpc.organisationseinheit.OrganisationsEinheitServiceGrpc.OrganisationsEinheitServiceBlockingStub; + +class OrganisationsEinheitRemoteServiceTest { + + @Spy + @InjectMocks + private OrganisationsEinheitRemoteService service; + + @Mock + private OrganisationsEinheitServiceBlockingStub serviceStub; + + @Mock + private OrganisationsEinheitHeaderMapper organisationsEinheitHeaderMapper; + @Mock + private OrganisationsEinheitMapper organisationsEinheitMapper; + + @Nested + class TestSearch { + + private final String searchBy = LoremIpsum.getInstance().getWords(5); + private final GrpcOrganisationsEinheitSearchResponse grpcOrganisationsEinheitSearchResponse = GrpcOrganisationsEinheitSearchResponseTestFactory + .create(); + + @BeforeEach + void setUpMocks() { + when(serviceStub.search(argThat(requestContainsSearchBy()))).thenReturn(grpcOrganisationsEinheitSearchResponse); + } + + @Test + void shouldCallGrpcStub() { + callService(); + + verify(serviceStub).search(argThat(requestContainsSearchBy())); + } + + @Test + void shouldCallMapper() { + callService().toList(); + + verify(organisationsEinheitHeaderMapper).fromGrpc(GrpcOrganisationsEinheitSearchResponseTestFactory.ORGANISATIONS_EINHEIT); + } + + @Test + void shouldReturnMappedOrganisationsEinheitHeader() { + var organisationsEinheitHeader = OrganisationsEinheitHeaderTestFactory.create(); + when(organisationsEinheitHeaderMapper.fromGrpc(GrpcOrganisationsEinheitSearchResponseTestFactory.ORGANISATIONS_EINHEIT)).thenReturn( + organisationsEinheitHeader); + + var organisationsEinheitHeaders = callService(); + + assertThat(organisationsEinheitHeaders).containsExactly(organisationsEinheitHeader); + } + + private Stream<OrganisationsEinheitHeader> callService() { + return service.search(searchBy); + } + + private ArgumentMatcher<GrpcOrganisationsEinheitSearchRequest> requestContainsSearchBy() { + return request -> request.getSearchBy().equals(searchBy); + } + } + + @Nested + class TestGetById { + + private final GrpcOrganisationsEinheitGetRequest request = GrpcOrganisationsEinheitGetRequestTestFactory.create(); + private final GrpcOrganisationsEinheitGetResponse response = GrpcOrganisationsEinheitGetResponseTestFactory.create(); + private final OrganisationsEinheit organisationsEinheit = OrganisationsEinheitTestFactory.create(); + + @BeforeEach + void setUp() { + doReturn(request).when(service).buildGetByIdRequest(OrganisationsEinheitTestFactory.ID); + when(serviceStub.getById(request)).thenReturn(response); + doReturn(organisationsEinheit).when(service).getOrganisationsEinheitFromGetByIdResponse(response); + } + + @Test + void shouldBuildRequest() { + callService(); + + verify(service).buildGetByIdRequest(OrganisationsEinheitTestFactory.ID); + } + + @Test + void shouldCallGrpcStub() { + callService(); + + verify(serviceStub).getById(request); + } + + @Test + void shouldBuildResponse() { + callService(); + + verify(service).getOrganisationsEinheitFromGetByIdResponse(response); + } + + @Test + void shouldReturnOrganisationsEinheit() { + var get = service.getById(OrganisationsEinheitTestFactory.ID); + + assertThat(get).isEqualTo(organisationsEinheit); + } + + private OrganisationsEinheit callService() { + return service.getById(OrganisationsEinheitTestFactory.ID); + } + } + + @Nested + class TestBuildGetByIdRequest { + + @Test + void shouldHaveId() { + var request = service.buildGetByIdRequest(OrganisationsEinheitTestFactory.ID); + + assertThat(request.getId()).isEqualTo(OrganisationsEinheitTestFactory.ID); + } + } + + @Nested + class TestGetOrganisationsEinheitFromGetByIdResponse { + + private final GrpcOrganisationsEinheitGetResponse response = GrpcOrganisationsEinheitGetResponseTestFactory.create(); + private final OrganisationsEinheit organisationsEinheit = OrganisationsEinheitTestFactory.create(); + + @BeforeEach + void setUp() { + when(organisationsEinheitMapper.fromGrpc(response.getOrganisationsEinheit())).thenReturn(organisationsEinheit); + } + + @Test + void shouldCallMapper() { + callService(); + + verify(organisationsEinheitMapper).fromGrpc(response.getOrganisationsEinheit()); + } + + @Test + void shouldReturnOrgnisationsEinheit() { + var built = callService(); + + assertThat(built).isEqualTo(organisationsEinheit); + } + + private OrganisationsEinheit callService() { + return service.getOrganisationsEinheitFromGetByIdResponse(response); + } + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitServiceTest.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a574a5e4aa0b7f3bbc3b0f4d90497d68d98b7b00 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitServiceTest.java @@ -0,0 +1,71 @@ +package de.ozgcloud.alfa.collaboration; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import com.thedeanda.lorem.LoremIpsum; + +class OrganisationsEinheitServiceTest { + + @InjectMocks + private OrganisationsEinheitService service; + + @Mock + private OrganisationsEinheitRemoteService remoteService; + + @Nested + class TestGetById { + + @Test + void shouldCallRemoteService() { + service.getById(OrganisationsEinheitTestFactory.ID); + + verify(remoteService).getById(OrganisationsEinheitTestFactory.ID); + } + + @Test + void shouldReturnOrganisationsEinheit() { + var organisationsEinheit = OrganisationsEinheitTestFactory.create(); + when(remoteService.getById(OrganisationsEinheitTestFactory.ID)).thenReturn(organisationsEinheit); + + var returnedOrganisationsEinheit = service.getById(OrganisationsEinheitTestFactory.ID); + + assertThat(returnedOrganisationsEinheit).isEqualTo(organisationsEinheit); + } + } + + @Nested + class TestSearchOrganisationsEinheiten { + + private final String searchBy = LoremIpsum.getInstance().getWords(5); + + @Test + void shouldCallRemoteService() { + callService(); + + verify(remoteService).search(searchBy); + } + + @Test + void shouldReturnOrganisationsEinheiten() { + var organisationsEinheitHeader = OrganisationsEinheitHeaderTestFactory.create(); + when(remoteService.search(searchBy)).thenReturn(Stream.of(organisationsEinheitHeader)); + + var organisationsEinheitHeaders = callService(); + + assertThat(organisationsEinheitHeaders).containsExactly(organisationsEinheitHeader); + } + + private Stream<OrganisationsEinheitHeader> callService() { + return service.searchOrganisationsEinheiten(searchBy); + } + } + +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..dea40a930f381d7a0ae3c45ba7af4cef19588a5a --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/collaboration/OrganisationsEinheitTestFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.ozgcloud.alfa.collaboration; + +import de.ozgcloud.alfa.collaboration.OrganisationsEinheit.OrganisationsEinheitBuilder; + +public class OrganisationsEinheitTestFactory { + + public static final String ID = OrganisationsEinheitHeaderTestFactory.ID; + + public static OrganisationsEinheit create() { + return createBuilder().build(); + } + + public static OrganisationsEinheitBuilder createBuilder() { + return OrganisationsEinheit.builder() + .id(ID); + } +} diff --git a/alfa-service/src/test/java/de/ozgcloud/alfa/common/EntityModelTestFactory.java b/alfa-service/src/test/java/de/ozgcloud/alfa/common/EntityModelTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..e280a99cb43da4647f9a8ebcb9cbbd753f80dee3 --- /dev/null +++ b/alfa-service/src/test/java/de/ozgcloud/alfa/common/EntityModelTestFactory.java @@ -0,0 +1,20 @@ +package de.ozgcloud.alfa.common; + +import org.springframework.hateoas.EntityModel; + +import de.ozgcloud.alfa.vorgang.VorgangWithEingang; +import lombok.NoArgsConstructor; + +public class EntityModelTestFactory { + + public static final NullableEntityModel NULLABLE = createNullable(); + + private static NullableEntityModel createNullable() { + return new NullableEntityModel(); + } + + @NoArgsConstructor + private static class NullableEntityModel extends EntityModel<VorgangWithEingang> { + + } +} diff --git a/pom.xml b/pom.xml index 334f0fbc2c8cf186962349557be289bcb2724de3..ea5ae9ff598c2dabd88fd1b3c1421b523a0f31fa 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,9 @@ unter der Lizenz sind dem Lizenztext zu entnehmen. --> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> @@ -54,6 +56,7 @@ <nachrichten-manager.version>2.7.0</nachrichten-manager.version> <ozgcloud-common-pdf.version>3.0.1</ozgcloud-common-pdf.version> <user-manager.version>2.2.0</user-manager.version> + <zufi-manager.version>1.2.0-SNAPSHOT</zufi-manager.version> <!-- TODO: die Version über ozgcloud-common ziehen --> <jjwt.version>0.11.5</jjwt.version> @@ -103,10 +106,15 @@ <artifactId>vorgang-manager-interface</artifactId> <version>${vorgang-manager.version}</version> </dependency> + <dependency> + <groupId>de.ozgcloud.zufi</groupId> + <artifactId>zufi-manager-interface</artifactId> + <version>${zufi-manager.version}</version> + </dependency> <dependency> <groupId>de.ozgcloud.nachrichten</groupId> <artifactId>nachrichten-manager-interface</artifactId> - <version>2.7.0</version> + <version>${nachrichten-manager.version}</version> </dependency> <dependency> <groupId>de.ozgcloud.vorgang</groupId> @@ -165,4 +173,4 @@ <url>https://nexus.ozg-sh.de/repository/ozg-snapshots/</url> </snapshotRepository> </distributionManagement> -</project> +</project> \ No newline at end of file diff --git a/src/main/helm/templates/deployment.yaml b/src/main/helm/templates/deployment.yaml index b7221ad6bd6407099a01862c2ffb85bb5199d49d..c21b9c17e45df3527a4550c51caffadb2295c26c 100644 --- a/src/main/helm/templates/deployment.yaml +++ b/src/main/helm/templates/deployment.yaml @@ -109,6 +109,10 @@ spec: value: {{ ((.Values.ozgcloud).xdomea).behoerdenschluesselUri}} - name: ozgcloud_xdomea_behoerdenschluesselVersion value: {{ ((.Values.ozgcloud).xdomea).behoerdenschluesselVersion | quote }} + {{- if ((.Values.ozgcloud).feature).collaborationEnabled }} + - name: ozgcloud_feature_collaborationEnabled + value: {{ ((.Values.ozgcloud).feature).collaborationEnabled | quote }} + {{- end }} image: "{{ .Values.image.repo }}/{{ .Values.image.name }}:{{ coalesce (.Values.image).tag "latest" }}" imagePullPolicy: Always diff --git a/src/main/helm/templates/network_policy.yaml b/src/main/helm/templates/network_policy.yaml index fde1ca628cf7ea984d4ffceefbdf13f4922c339a..ee37649c47d584833c401a8f68748ba46a4d8fd2 100644 --- a/src/main/helm/templates/network_policy.yaml +++ b/src/main/helm/templates/network_policy.yaml @@ -21,6 +21,18 @@ spec: {{ toYaml . | indent 2 }} {{- end }} egress: +{{- if ((.Values.ozgcloud).feature).collaborationEnabled }} + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ required "zufiManager.namespace must be set if zufiManager server is enabled" (.Values.zufiManager).namespace }} + podSelector: + matchLabels: + component: zufi-server + ports: + - port: 9090 + protocol: TCP +{{- end }} - to: - podSelector: matchLabels: diff --git a/src/test/helm/deployment_collaboration_env_test.yaml b/src/test/helm/deployment_collaboration_env_test.yaml new file mode 100644 index 0000000000000000000000000000000000000000..21ca8ba4550390d89e333fb62a3110e389615f81 --- /dev/null +++ b/src/test/helm/deployment_collaboration_env_test.yaml @@ -0,0 +1,38 @@ +suite: deployment collaboration env +release: + name: alfa + namespace: sh-helm-test +templates: + - templates/deployment.yaml +set: + baseUrl: test.company.local + ozgcloud: + environment: test + bundesland: sh + bezeichner: helm + sso: + serverUrl: https://sso.company.local + imagePullSecret: image-pull-secret +tests: + - it: should enable collaboration + set: + ozgcloud: + feature: + collaborationEnabled: true + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_feature_collaborationEnabled + value: "true" + - it: should not enable collaboration + set: + ozgcloud: + feature: + collaborationEnabled: false + asserts: + - notContains: + path: spec.template.spec.containers[0].env + content: + name: ozgcloud_feature_collaborationEnabled + any: true \ No newline at end of file diff --git a/src/test/helm/network_policy_test.yaml b/src/test/helm/network_policy_test.yaml index af65c804e62b7fb9aebb26f65f2322b536f71550..b1b35ecddd7608c33ed412b7a036829d4e966bb6 100644 --- a/src/test/helm/network_policy_test.yaml +++ b/src/test/helm/network_policy_test.yaml @@ -78,7 +78,7 @@ tests: - port: 8080 egress: - to: - - podSelector: + - podSelector: matchLabels: component: vorgang-manager ports: @@ -223,4 +223,40 @@ tests: dnsServerNamespace: test-dns-server-namespace asserts: - hasDocuments: - count: 1 \ No newline at end of file + count: 1 + + - it: should set egress for zufi if configured + set: + networkPolicy: + ssoPublicIp: 1.1.1.1 + dnsServerNamespace: test-dns-server-namespace + ozgcloud: + feature: + collaborationEnabled: true + zufiManager: + namespace: by-zufi-dev + asserts: + - contains: + path: spec.egress + content: + to: + - podSelector: + matchLabels: + component: zufi-server + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: by-zufi-dev + ports: + - port: 9090 + protocol: TCP + - it: should fail to set egress for zufi if namespace is missing + set: + networkPolicy: + ssoPublicIp: 1.1.1.1 + dnsServerNamespace: test-dns-server-namespace + ozgcloud: + feature: + collaborationEnabled: true + asserts: + - failedTemplate: + errorMessage: zufiManager.namespace must be set if zufiManager server is enabled \ No newline at end of file