Skip to content
Snippets Groups Projects
Commit 4fcb8cb5 authored by Alexander Reifschneider's avatar Alexander Reifschneider
Browse files

Merge branch 'main' into OZG-6564-new-edit-button

parents 841c6960 3e198080
Branches
Tags
1 merge request!48OZG-6564 new edit button
Showing
with 502 additions and 46 deletions
FROM nginx:alpine
WORKDIR /etc/nginx
COPY ./nginx-alfa-cors-proxy.conf ./conf.d/default.conf
EXPOSE 80
ENTRYPOINT [ "nginx" ]
CMD [ "-g", "daemon off;" ]
\ No newline at end of file
## Allgemein # Admin E2E Tests
Dieses Teilprojekt enthält die End-2-End Tests für die Admin-Anwendung. Dieses Teilprojekt enthält die End-2-End Tests für die Admin-Anwendung.
...@@ -7,3 +7,30 @@ Der Aufbau ist analog der [E2E-Tests für Alfa](../alfa-e2e/README.md). ...@@ -7,3 +7,30 @@ Der Aufbau ist analog der [E2E-Tests für Alfa](../alfa-e2e/README.md).
Bei Ausführung mit Jenkins im Cluster wird das [Jenkinsfile.e2e](../../Jenkinsfile.e2e) verwendet. Bei Ausführung mit Jenkins im Cluster wird das [Jenkinsfile.e2e](../../Jenkinsfile.e2e) verwendet.
Bei lokaler Ausführung die hier abgelegte [docker-compose.yml](docker-compose.yml) verwenden. Bei lokaler Ausführung die hier abgelegte [docker-compose.yml](docker-compose.yml) verwenden.
## Zugriff zu zufi Daten (Organisationseinheiten und externe Fachstellen)
### TL;DR
```bash
cd apps
./forward-to-zufi.sh
```
### Version Anforderungen
```bash
$ oc version
Client Version: v4.2.0-alpha.0-2348-ge8fb3c0
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Kubernetes Version: v1.30.7-dirty
```
### Erklärung
Die zufi Daten d.h. die Organisationseinheiten und externe Fachstellen werden für die E2E Tests Zwecke vom zufi Service aus dem DEV Cluster geholt.
Es wird kein zufi Service für die lokale E2E Tests Entwicklung gestartet. Damit die Kommunikation mit dem zufi Service funktioniert muss man
einfach das Skript `forward-to-zufi.sh` starten. Nach dem Ausführen wird die Login Seite geöffnet. Nach dem erfolgreichen Anmelden kann man schon
die Tests lokal ausführen.
Wenn meine keine E2E Tests entwickelt die zufi Daten benötigen, dann braucht das auch nicht machen.
\ No newline at end of file
...@@ -38,14 +38,111 @@ services: ...@@ -38,14 +38,111 @@ services:
timeout: 5s timeout: 5s
retries: 5 retries: 5
alfa:
image: docker.ozg-sh.de/alfa:${ALFA_DOCKER_IMAGE:-snapshot-latest}
platform: linux/amd64
environment:
- GRPC_CLIENT_USER-MANAGER_ADDRESS=static://user-manager:9000
- GRPC_CLIENT_USER-MANAGER_NEGOTIATIONTYPE=PLAINTEXT
- GRPC_CLIENT_VORGANG-MANAGER_ADDRESS=static://vorgang-manager:9090
- GRPC_CLIENT_VORGANG-MANAGER_NEGOTIATIONTYPE=PLAINTEXT
- GRPC_CLIENT_COLLABORATION-MANAGER_ADDRESS=static://vorgang-manager:9090
- GRPC_CLIENT_COLLABORATION-MANAGER_NEGOTIATIONTYPE=PLAINTEXT
- GRPC_CLIENT_ARCHIVE-MANAGER_ADDRESS=static://vorgang-manager:9090
- GRPC_CLIENT_ARCHIVE-MANAGER_NEGOTIATIONTYPE=PLAINTEXT
- GRPC_CLIENT_ZUFI-MANAGER_ADDRESS=static://host.docker.internal:9190
- GRPC_CLIENT_ZUFI-MANAGER_NEGOTIATIONTYPE=PLAINTEXT
- KEYCLOAK_AUTH_SERVER_URL=https://sso.dev.by.ozg-cloud.de
- KEYCLOAK_REALM=${KEYCLOAK_REALM:-by-e2e-tests-local-dev}
- KEYCLOAK_RESOURCE=${KEYCLOAK_CLIENT:-alfa}
- OZGCLOUD_FEATURE_VORGANG_EXPORT=true
- OZGCLOUD_USER-ASSISTANCE_DOCUMENTATION_URL=/assets/benutzerleitfaden/Benutzerleitfaden_2.5.pdf
- OZGCLOUD_USER-MANAGER_URL=http://localhost:9092
- OZGCLOUD_VORGANG_PROCESSOR_0_FORM_ENGINE_NAME=AFM
- OZGCLOUD_VORGANG_PROCESSOR_0_FORM_ID=Erstattung_ERFOLG
- OZGCLOUD_VORGANG_PROCESSOR_1_FORM_ENGINE_NAME=AFM
- OZGCLOUD_VORGANG_PROCESSOR_1_FORM_ID=Erstattung_FAIL
- OZGCLOUD_VORGANG_PROCESSOR_NAMES_0=ticketCheck
- OZGCLOUD_DMS_ENABLED=true
- SPRING_PROFILES_ACTIVE=remotekc, e2e
- LOGGING_CONFIG=classpath:log4j2-local.xml
- BPL_DEBUG_ENABLED=true
- BPL_DEBUG_PORT=5000
- OZGCLOUD_VORGANG_BESCHEID_0_FORM_ENGINE_NAME=FormSolutions
- OZGCLOUD_VORGANG_BESCHEID_0_FORM_ID=KFAS_STAGE_KI_10_Haltverbot_LANDESHACKATHON
- OZGCLOUD_FEATURE_COLLABORATION_ENABLED=true
- OZGCLOUD_BARRIEREFREIHEIT_URL=https://static.dev.sh.ozg-cloud.de/barrierefreiheit
# - LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_SECURITY=TRACE
ports:
- 8081:8080
- 5000:5000
depends_on:
- user-manager
extra_hosts:
- "host.docker.internal:host-gateway"
alfa-cors-proxy:
image: alfa-cors-proxy
build:
dockerfile: Dockerfile-nginx
ports:
- 8082:80
depends_on:
alfa:
condition: service_started
user-manager:
image: docker.ozg-sh.de/user-manager:${USER_MANAGER_DOCKER_IMAGE:-snapshot-latest}
platform: linux/amd64
environment:
- KEYCLOAK_URL=https://sso.dev.by.ozg-cloud.de
- OZGCLOUD_KEYCLOAK_API_CLIENT=alfa
- OZGCLOUD_KEYCLOAK_API_REALM=${KEYCLOAK_REALM:-by-e2e-tests-local-dev}
- OZGCLOUD_KEYCLOAK_API_USER=usermanagerapiuser
- OZGCLOUD_KEYCLOAK_API_PASSWORD=${OZGCLOUD_KEYCLOAK_API_PASSWORD:-}
- OZGCLOUD_USER_MANAGER_URL=http://localhost:9092
- OZGCLOUD_USERSYNC_PERIOD=disabled
- OZGCLOUD_USERSYNC_ONSTART=false
- QUARKUS_GRPC_SERVER_SSL_CERTIFICATE=
- QUARKUS_GRPC_SERVER_SSL_KEY=
- QUARKUS_HTTP_CORS_ORIGINS=http://localhost:4300,http://127.0.0.1:4300,https://e2e.dev.by.ozg-cloud.de,http://localhost:8080
- QUARKUS_LOG_CONSOLE_JSON=false
- QUARKUS_MONGODB_CONNECTION_STRING=mongodb://mongodb:27017
- QUARKUS_MONGODB_DATABASE=usermanager
- QUARKUS_OIDC_AUTH_SERVER_URL=https://sso.dev.by.ozg-cloud.de/realms/${KEYCLOAK_REALM:-by-e2e-tests-local-dev}
- QUARKUS_OIDC_CLIENT_ID=alfa
- quarkus.log.category."io.quarkus.oidc.runtime.OidcProvider".level=TRACE
- quarkus.log.category."io.quarkus.oidc.runtime.OidcProvider".min-level=TRACE
- quarkus.log.category."io.grpc.netty.shaded.io.grpc".level=TRACE
ports:
- 9092:8080
- 9000:9000
depends_on:
mongodb:
condition: service_healthy
administration: administration:
image: docker.ozg-sh.de/administration:${ADMINISTRATION_DOCKER_IMAGE:-snapshot-latest} image: docker.ozg-sh.de/administration:${ADMINISTRATION_DOCKER_IMAGE:-snapshot-latest}
platform: linux/amd64 platform: linux/amd64
environment: environment:
- GRPC_CLIENT_ZUFI-MANAGER_ADDRESS=static://host.docker.internal:9190
- GRPC_CLIENT_ZUFI-MANAGER_NEGOTIATIONTYPE=PLAINTEXT
- SPRING_PROFILES_ACTIVE=${SPRING_PROFILE:-local,remotekc} - SPRING_PROFILES_ACTIVE=${SPRING_PROFILE:-local,remotekc}
- SPRING_DATA_MONGODB_URI=mongodb://mongodb:27017/config-db - SPRING_DATA_MONGODB_URI=mongodb://mongodb:27017/config-db
- OZGCLOUD_OAUTH2_REALM=${KEYCLOAK_REALM:-by-e2e-tests-local-dev}
- ozgcloud_organisationeinheit_zufisearchuri=http://localhost:8082/api/organisationseinheits
- BPL_DEBUG_ENABLED=true
- BPL_DEBUG_PORT=5100
ports: ports:
- 8080:8080 - 8080:8080
- 5100:5100
depends_on: depends_on:
mongodb: mongodb:
condition: service_healthy condition: service_healthy
alfa:
condition: service_started
user-manager:
condition: service_started
extra_hosts:
- "host.docker.internal:host-gateway"
upstream alfa {
server alfa:8080;
}
server {
listen 80;
server_name localhost;
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,
X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
add_header 'Content-Type' 'application/json';
add_header 'Content-Length' 0;
return 204;
}
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,
X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
proxy_set_header 'Origin' '';
proxy_pass http://alfa;
}
}
\ No newline at end of file
...@@ -27,6 +27,7 @@ export class OrganisationsEinheitenE2EComponent { ...@@ -27,6 +27,7 @@ export class OrganisationsEinheitenE2EComponent {
private readonly organisationsEinheitenList: string = 'organisations-einheit-list'; private readonly organisationsEinheitenList: string = 'organisations-einheit-list';
private readonly organisationsEinheitenName: string = 'organisations-einheit-name'; private readonly organisationsEinheitenName: string = 'organisations-einheit-name';
private readonly organisationsEinheitenID: string = 'organisations-einheit-id'; private readonly organisationsEinheitenID: string = 'organisations-einheit-id';
private readonly organisationsEinheitHinzufuegen: string = 'add-organisationseinheit-button';
private readonly errorColor: string = 'text-red-500'; private readonly errorColor: string = 'text-red-500';
private readonly errorIcon: string = 'organisations-einheit-sync-error'; private readonly errorIcon: string = 'organisations-einheit-sync-error';
...@@ -44,6 +45,14 @@ export class OrganisationsEinheitenE2EComponent { ...@@ -44,6 +45,14 @@ export class OrganisationsEinheitenE2EComponent {
this.getListItemByName(name).click(); this.getListItemByName(name).click();
} }
public getOrganisationsEinheitHinzufuegenButton(): Cypress.Chainable<Element> {
return cy.getTestElement(this.organisationsEinheitHinzufuegen);
}
public clickHinzufuegen(): void {
this.getOrganisationsEinheitHinzufuegenButton().click();
}
public organisationsEinheitContainsID(name: string, id: string): void { public organisationsEinheitContainsID(name: string, id: string): void {
this.getListItemByName(name) this.getListItemByName(name)
.parents('a') .parents('a')
......
import { exist } from '../../support/cypress.util';
export class StatistikE2EComponent {
private readonly headerText: string = 'statistik-header-text';
public isHeaderTextVisible(): void {
exist(cy.getTestElement(this.headerText));
}
}
import { enterWith } from '../../support/cypress.util';
export class StatistikFieldsFormE2EComponent {
private readonly locatorFormEngineInput: string = 'form-engine-input';
private readonly locatorFormIdInput: string = 'form-id-input';
private readonly locatorFormDataFieldInput: string = 'data-statistik-field-';
private readonly locatorAddFieldButton: string = 'add-data-field-button';
private readonly locatorSaveButton: string = 'save-statistik-fields-button';
private readonly locatorCancelButton: string = 'cancel-statistik-fields-button';
public getFormEngineInput(): Cypress.Chainable<Element> {
return cy.getTestElement(this.locatorFormEngineInput);
}
public enterFormEngine(text: string): void {
enterWith(this.getFormEngineInput(), text);
}
public getFormIdInput(): Cypress.Chainable<Element> {
return cy.getTestElement(this.locatorFormIdInput);
}
public enterFormId(text: string): void {
enterWith(this.getFormIdInput(), text);
}
public getAddFieldButton(): Cypress.Chainable<Element> {
return cy.getTestElement(this.locatorAddFieldButton);
}
public addField(): void {
this.getAddFieldButton().click();
}
public getDataFieldInput(index: number): Cypress.Chainable<Element> {
return cy.getTestElement(this.locatorFormDataFieldInput + index);
}
public enterDataFieldPath(text: string, index: number): void {
enterWith(this.getDataFieldInput(index), text);
}
public getSaveButton(): Cypress.Chainable<Element> {
return cy.getTestElement(this.locatorSaveButton);
}
public save(): void {
this.getSaveButton().click();
}
public getCancelButton(): Cypress.Chainable<Element> {
return cy.getTestElement(this.locatorCancelButton);
}
public cancel(): void {
this.getCancelButton().click();
}
}
import { exist } from '../../support/cypress.util';
export class StatistikE2EComponent {
private readonly locatorHeaderText: string = 'statistik-header-text';
private readonly locatorWeitereFelderAuswertenButton = 'weitere-felder-auswerten-button';
public isHeaderTextVisible(): void {
exist(cy.getTestElement(this.locatorHeaderText));
}
public getWeiterFelderAuswertenButton(): Cypress.Chainable<Element> {
return cy.getTestElement(this.locatorWeitereFelderAuswertenButton);
}
}
import { typeText } from '../../support/cypress.util';
export class ZustaendigeStelleDialogE2EComponent {
private readonly locatorZustaendigeStelleForm: string = 'search-organisations-einheit';
private readonly locatorSearchInput: string = 'instant_search-text-input';
private readonly locatorFoundItemButton: string = 'item-button';
private readonly locatorFoundItemTitleParagraph: string = 'item-button-title';
public getZustaendigeStelleForm(): Cypress.Chainable<Element> {
return cy.getTestElement(this.locatorZustaendigeStelleForm);
}
public getSearchInput(): Cypress.Chainable<Element> {
return cy.getTestElement(this.locatorSearchInput);
}
public enterSearchTerm(searchTerm: string): void {
typeText(this.getSearchInput(), searchTerm);
}
public countSearchEntries(): Cypress.Chainable<number> {
// @ts-ignore
return cy.getTestElement(this.locatorFoundItemButton).then((entries: HTMLButtonElement[]) => {
return Cypress.$(entries).length;
});
}
public expectNumberOfEntriesToBeGreaterThan(entries: number): void {
this.countSearchEntries().then((count) => {
expect(count).to.greaterThan(entries);
});
}
public getZustaendigeStelleTitle(index: number): Cypress.Chainable<string> {
return cy
.getChildTestElementFromListElement(this.locatorFoundItemButton, index, this.locatorFoundItemTitleParagraph)
.then((entries) => entries[0].innerText);
}
public clickFoundItem(index: number): void {
cy.getTestElement(this.locatorFoundItemButton).eq(index).click();
}
}
import { StatistikE2EComponent } from 'apps/admin-e2e/src/components/statistik/statistik-component';
import { MainPage, waitForSpinnerToDisappear } from 'apps/admin-e2e/src/page-objects/main.po'; import { MainPage, waitForSpinnerToDisappear } from 'apps/admin-e2e/src/page-objects/main.po';
import { exist, notExist } from 'apps/admin-e2e/src/support/cypress.util'; import { exist, notExist } from 'apps/admin-e2e/src/support/cypress.util';
import { loginAsDaria } from 'apps/admin-e2e/src/support/user-util'; import { loginAsDaria } from 'apps/admin-e2e/src/support/user-util';
import { StatistikE2EComponent } from '../../../components/statistik/statistik.e2e.component';
describe('Navigation', () => { describe('Navigation', () => {
const mainPage: MainPage = new MainPage(); const mainPage: MainPage = new MainPage();
......
import { StatistikE2EComponent } from 'apps/admin-e2e/src/components/statistik/statistik-component';
import { MainPage, waitForSpinnerToDisappear } from 'apps/admin-e2e/src/page-objects/main.po'; import { MainPage, waitForSpinnerToDisappear } from 'apps/admin-e2e/src/page-objects/main.po';
import { exist } from 'apps/admin-e2e/src/support/cypress.util'; import { exist } from 'apps/admin-e2e/src/support/cypress.util';
import { loginAsSafira } from 'apps/admin-e2e/src/support/user-util'; import { loginAsSafira } from 'apps/admin-e2e/src/support/user-util';
import { StatistikE2EComponent } from '../../../components/statistik/statistik.e2e.component';
describe('Navigation', () => { describe('Navigation', () => {
const mainPage: MainPage = new MainPage(); const mainPage: MainPage = new MainPage();
......
import { OrganisationsEinheitenE2EComponent } from '../../../components/organisationseinheiten/organisationseinheiten.e2e.component';
import { ZustaendigeStelleDialogE2EComponent } from '../../../components/zustaendige-stelle/zustaendige-stelle-dialog.e2e.component';
import { MainPage, waitForSpinnerToDisappear } from '../../../page-objects/main.po';
import { exist } from '../../../support/cypress.util';
import { loginAsAriane } from '../../../support/user-util';
describe('Organisationseinheiten', () => {
const mainPage: MainPage = new MainPage();
const organisationsEinheitenComponent: OrganisationsEinheitenE2EComponent = new OrganisationsEinheitenE2EComponent();
const zustaendigeStelleSearchComponent: ZustaendigeStelleDialogE2EComponent = new ZustaendigeStelleDialogE2EComponent();
const searchTerm: string = 'Hamburg';
before(() => {
loginAsAriane();
});
it('should show table with Organisationseinheiten', () => {
waitForSpinnerToDisappear();
mainPage.openOrganisationsEinheiten();
exist(organisationsEinheitenComponent.getOrganisationsEinheitHinzufuegenButton());
});
it('should show button to add Organisationseinheit', () => {
exist(organisationsEinheitenComponent.getOrganisationsEinheitHinzufuegenButton());
});
it('should show search Organisationseinheit dialog', () => {
organisationsEinheitenComponent.clickHinzufuegen();
exist(zustaendigeStelleSearchComponent.getZustaendigeStelleForm());
});
it('should find at least one Organisationseinheit', () => {
zustaendigeStelleSearchComponent.enterSearchTerm(searchTerm);
zustaendigeStelleSearchComponent.expectNumberOfEntriesToBeGreaterThan(1);
});
it('should add first Organisationseinheit', () => {
zustaendigeStelleSearchComponent.getZustaendigeStelleTitle(0).then((title: string) => {
zustaendigeStelleSearchComponent.clickFoundItem(0);
exist(organisationsEinheitenComponent.getListItemByName(title));
});
});
});
import { ROUTES } from '@admin-client/shared';
import { StatistikFieldsFormE2EComponent } from '../../../components/statistik/statistik-fields-form.e2e.component';
import { StatistikE2EComponent } from '../../../components/statistik/statistik.e2e.component';
import { urlShouldEndsWith } from '../../../support/cypress-helper';
import { exist } from '../../../support/cypress.util';
import { loginAsDaria } from '../../../support/user-util';
describe('Felder in Statistik hinzufügen', () => {
const component: StatistikE2EComponent = new StatistikE2EComponent();
const fieldsFormComponent: StatistikFieldsFormE2EComponent = new StatistikFieldsFormE2EComponent();
before(() => {
loginAsDaria();
});
it('should be on statistik page', () => {
urlShouldEndsWith(ROUTES.STATISTIK);
});
it('should show statistik header', () => {
component.isHeaderTextVisible();
});
it('should show "Weiter Felder auswerten" button', () => {
exist(component.getWeiterFelderAuswertenButton());
});
it('should navigate to route', () => {
component.getWeiterFelderAuswertenButton().click();
urlShouldEndsWith(ROUTES.STATISTIK_NEU);
});
it('should have all form elements', () => {
exist(fieldsFormComponent.getFormEngineInput());
exist(fieldsFormComponent.getFormIdInput());
exist(fieldsFormComponent.getDataFieldInput(0));
exist(fieldsFormComponent.getAddFieldButton());
exist(fieldsFormComponent.getSaveButton());
exist(fieldsFormComponent.getCancelButton());
});
it('should add data field', () => {
fieldsFormComponent.addField();
exist(fieldsFormComponent.getDataFieldInput(1));
});
it('should navigate to statistik on cancel', () => {
fieldsFormComponent.cancel();
urlShouldEndsWith(ROUTES.STATISTIK);
});
});
...@@ -51,6 +51,36 @@ administration: ...@@ -51,6 +51,36 @@ administration:
role: query-groups role: query-groups
- name: realm-management - name: realm-management
role: manage-users role: manage-users
- name: realm-management
role: view-clients
- name: daria
email: daria.data@ozg-sh.de
first_name: Daria
last_name: Data
password: 'Y9nk43yrQ_zzIPpfFU-I'
update_user: true
groups: []
client_roles:
- name: admin
role: DATENBEAUFTRAGUNG
- name: safira
email: safira-super@ozg-sh.de
first_name: Safira
last_name: Super
password: 'Y9nk43yrQ_zzIPpfFU-I'
update_user: true
groups: []
client_roles:
- name: admin
role: ADMIN_ADMIN
- name: admin
role: DATENBEAUFTRAGUNG
- name: realm-management
role: query-groups
- name: realm-management
role: manage-users
- name: realm-management
role: view-clients
alfa_client: alfa_client:
enabled: true enabled: true
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
* Die sprachspezifischen Genehmigungen und Beschränkungen * Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen. * unter der Lizenz sind dem Lizenztext zu entnehmen.
*/ */
/// <reference types="cypress" /> /// <reference types="cypress" />
enum HttpMethod { enum HttpMethod {
...@@ -50,6 +51,13 @@ Cypress.Commands.add('getTestElement', (selector, ...args) => { ...@@ -50,6 +51,13 @@ Cypress.Commands.add('getTestElement', (selector, ...args) => {
return cy.get(`[${DATA_TEST_ID}~="${selector}"]`, ...args); return cy.get(`[${DATA_TEST_ID}~="${selector}"]`, ...args);
}); });
Cypress.Commands.add('getChildTestElementFromListElement', (parentSelector, index, childSelector, ...args) => {
return cy
.get(`[${DATA_TEST_ID}~="${parentSelector}"]`, ...args)
.eq(index)
.find(`[${DATA_TEST_ID}~="${childSelector}"]`);
});
Cypress.Commands.add('getTestElementWithClass', (selector, ...args) => { Cypress.Commands.add('getTestElementWithClass', (selector, ...args) => {
console.log( console.log(
'Achtung: Potentiell nicht eindeutiges Ergebnis, weil eine data-test-class mit cy.get() von der DOM-Root aus gesucht wird.', 'Achtung: Potentiell nicht eindeutiges Ergebnis, weil eine data-test-class mit cy.get() von der DOM-Root aus gesucht wird.',
...@@ -61,13 +69,9 @@ Cypress.Commands.add('getTestElementWithOid', (oid, ...args) => { ...@@ -61,13 +69,9 @@ Cypress.Commands.add('getTestElementWithOid', (oid, ...args) => {
return cy.getTestElement(oid, ...args); return cy.getTestElement(oid, ...args);
}); });
Cypress.Commands.add( Cypress.Commands.add('findTestElementWithClass', { prevSubject: true }, (subject: any, selector) => {
'findTestElementWithClass',
{ prevSubject: true },
(subject: any, selector) => {
return subject.find(`[${DATA_TEST_CLASS}="${selector}"]`); return subject.find(`[${DATA_TEST_CLASS}="${selector}"]`);
}, });
);
Cypress.Commands.add('findElement', { prevSubject: true }, (subject: any, selector: string) => { Cypress.Commands.add('findElement', { prevSubject: true }, (subject: any, selector: string) => {
return subject.find(selector); return subject.find(selector);
...@@ -87,19 +91,29 @@ function getKeycloakBaseRealmUrl(): string { ...@@ -87,19 +91,29 @@ function getKeycloakBaseRealmUrl(): string {
return `${Cypress.env(CypressEnv.KEYCLOAK_URL)}realms/${Cypress.env(CypressEnv.KEYCLOAK_REALM)}/protocol/openid-connect`; return `${Cypress.env(CypressEnv.KEYCLOAK_URL)}realms/${Cypress.env(CypressEnv.KEYCLOAK_REALM)}/protocol/openid-connect`;
} }
type GetOptions = Partial< type GetOptions = Partial<Cypress.Loggable & Cypress.Timeoutable & Cypress.Withinable & Cypress.Shadow>;
Cypress.Loggable & Cypress.Timeoutable & Cypress.Withinable & Cypress.Shadow
>;
declare global { declare global {
// eslint-disable-next-line // eslint-disable-next-line
namespace Cypress { namespace Cypress {
interface Chainable { interface Chainable {
getTestElement(selector: string, ...args: GetOptions[]): Chainable<Element>; getTestElement(selector: string, ...args: GetOptions[]): Chainable<Element>;
getChildTestElementFromListElement(
parentSelector: string,
index: number,
childSelector: string,
...args: GetOptions[]
): Chainable<Element>;
getTestElementWithClass(selector: string, ...args: GetOptions[]): Chainable<Element>; getTestElementWithClass(selector: string, ...args: GetOptions[]): Chainable<Element>;
getTestElementWithOid(oid: string, ...args: GetOptions[]): Chainable<Element>; getTestElementWithOid(oid: string, ...args: GetOptions[]): Chainable<Element>;
findTestElementWithClass(selector: string): Chainable<Element>; findTestElementWithClass(selector: string): Chainable<Element>;
findElement(selector: string): Chainable<Element>; findElement(selector: string): Chainable<Element>;
getUserInfo(): Chainable<void>; getUserInfo(): Chainable<void>;
} }
} }
......
...@@ -67,6 +67,10 @@ export function urlShouldInclude(text: string) { ...@@ -67,6 +67,10 @@ export function urlShouldInclude(text: string) {
return cy.url().should('include', text); return cy.url().should('include', text);
} }
export function urlShouldEndsWith(route: string): Cypress.Chainable<any> {
return cy.url().should('match', new RegExp(`/${route}$`));
}
//TODO: anders loesen -> bad practice //TODO: anders loesen -> bad practice
export function wait(ms: number, reason = ''): void { export function wait(ms: number, reason = ''): void {
cy.wait(ms); cy.wait(ms);
......
{ {
"name": "admin", "name": "admin",
"version": "1.4.0" "version": "1.5.0-SNAPSHOT"
} }
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
<div class="flex h-screen w-full justify-center overflow-y-auto"> <div class="flex h-screen w-full justify-center overflow-y-auto">
<ods-navbar data-test-id="navigation"> <ods-navbar data-test-id="navigation">
@if (apiRoot | hasLink: apiRootLinkRel.USERS) { @if (apiRoot | hasLink: apiRootLinkRel.USERS) {
<ods-nav-item data-test-id="users-roles-navigation" caption="Benutzer & Rollen" path="/benutzer_und_rollen"> <ods-nav-item data-test-id="users-roles-navigation" caption="Benutzer & Rollen" [path]="routes.BENUTZER_UND_ROLLEN">
<ods-users-icon class="stroke-text" icon /> <ods-users-icon class="stroke-text" icon />
</ods-nav-item> </ods-nav-item>
} }
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
<ods-nav-item <ods-nav-item
data-test-id="organisations-einheiten-navigation" data-test-id="organisations-einheiten-navigation"
caption="Organisationseinheiten" caption="Organisationseinheiten"
path="/organisationseinheiten" [path]="routes.ORGANISATIONSEINHEITEN"
> >
<ods-orga-unit-icon icon /> <ods-orga-unit-icon icon />
</ods-nav-item> </ods-nav-item>
......
...@@ -22,27 +22,22 @@ ...@@ -22,27 +22,22 @@
* unter der Lizenz sind dem Lizenztext zu entnehmen. * unter der Lizenz sind dem Lizenztext zu entnehmen.
*/ */
import { ConfigurationLinkRel, ConfigurationResource, ConfigurationService } from '@admin-client/configuration-shared'; import { ConfigurationLinkRel, ConfigurationResource, ConfigurationService } from '@admin-client/configuration-shared';
import { ROUTES } from '@admin-client/shared';
import { KeycloakTokenService } from '@admin/keycloak-shared';
import { ApiRootLinkRel, ApiRootResource, ApiRootService } from '@alfa-client/api-root-shared'; import { ApiRootLinkRel, ApiRootResource, ApiRootService } from '@alfa-client/api-root-shared';
import { BuildInfoComponent } from '@alfa-client/common'; import { BuildInfoComponent } from '@alfa-client/common';
import { HasLinkPipe, createEmptyStateResource, createStateResource } from '@alfa-client/tech-shared'; import { createEmptyStateResource, createStateResource, HasLinkPipe } from '@alfa-client/tech-shared';
import { Mock, existsAsHtmlElement, mock, notExistsAsHtmlElement } from '@alfa-client/test-utils'; import { existsAsHtmlElement, getElementComponentFromFixtureByCss, Mock, mock, notExistsAsHtmlElement, } from '@alfa-client/test-utils';
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';
import { AuthenticationService } from '@authentication'; import { AuthenticationService } from '@authentication';
import { import { AdminLogoIconComponent, MailboxIconComponent, NavbarComponent, NavItemComponent, OrgaUnitIconComponent, UsersIconComponent, } from '@ods/system';
AdminLogoIconComponent,
MailboxIconComponent,
NavItemComponent,
NavbarComponent,
OrgaUnitIconComponent,
UsersIconComponent,
} from '@ods/system';
import { createConfigurationResource } from 'libs/admin/configuration-shared/test/configuration'; import { createConfigurationResource } from 'libs/admin/configuration-shared/test/configuration';
import { MenuContainerComponent } from 'libs/admin/configuration/src/lib/menu-container/menu-container.component'; import { MenuContainerComponent } from 'libs/admin/configuration/src/lib/menu-container/menu-container.component';
import { createApiRootResource } from 'libs/api-root-shared/test/api-root'; import { createApiRootResource } from 'libs/api-root-shared/test/api-root';
import { getDataTestIdOf } from 'libs/tech-shared/test/data-test'; import { getDataTestIdOf } from 'libs/tech-shared/test/data-test';
import { MockComponent, MockDirective } from 'ng-mocks'; import { MockComponent, MockDirective } from 'ng-mocks';
import { Subscription, of } from 'rxjs'; import { of, Subscription } from 'rxjs';
import { UserProfileButtonContainerComponent } from '../common/user-profile-button-container/user-profile.button-container.component'; import { UserProfileButtonContainerComponent } from '../common/user-profile-button-container/user-profile.button-container.component';
import { UnavailablePageComponent } from '../pages/unavailable/unavailable-page/unavailable-page.component'; import { UnavailablePageComponent } from '../pages/unavailable/unavailable-page/unavailable-page.component';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
...@@ -80,9 +75,11 @@ describe('AppComponent', () => { ...@@ -80,9 +75,11 @@ describe('AppComponent', () => {
const apiRootService: Mock<ApiRootService> = mock(ApiRootService); const apiRootService: Mock<ApiRootService> = mock(ApiRootService);
let configurationService: Mock<ConfigurationService>; let configurationService: Mock<ConfigurationService>;
let keycloakTokenService: Mock<KeycloakTokenService>;
beforeEach(async () => { beforeEach(async () => {
configurationService = mock(ConfigurationService); configurationService = mock(ConfigurationService);
keycloakTokenService = mock(KeycloakTokenService);
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [HasLinkPipe], imports: [HasLinkPipe],
...@@ -121,6 +118,10 @@ describe('AppComponent', () => { ...@@ -121,6 +118,10 @@ describe('AppComponent', () => {
provide: ActivatedRoute, provide: ActivatedRoute,
useValue: route, useValue: route,
}, },
{
provide: KeycloakTokenService,
useValue: keycloakTokenService,
},
], ],
}).compileComponents(); }).compileComponents();
}); });
...@@ -165,9 +166,13 @@ describe('AppComponent', () => { ...@@ -165,9 +166,13 @@ describe('AppComponent', () => {
expect(apiRootService.getApiRoot).toHaveBeenCalled(); expect(apiRootService.getApiRoot).toHaveBeenCalled();
}); });
it('should call evaluateInitialNavigation', () => { it('should call keycloak token service to register token provider', () => {
component.evaluateInitialNavigation = jest.fn(); component.doAfterLoggedIn();
expect(keycloakTokenService.registerAccessTokenProvider).toHaveBeenCalled();
});
it('should call evaluateInitialNavigation', () => {
component.doAfterLoggedIn(); component.doAfterLoggedIn();
expect(component.evaluateInitialNavigation).toHaveBeenCalled(); expect(component.evaluateInitialNavigation).toHaveBeenCalled();
...@@ -369,6 +374,20 @@ describe('AppComponent', () => { ...@@ -369,6 +374,20 @@ describe('AppComponent', () => {
describe('navigation', () => { describe('navigation', () => {
describe('user and roles', () => { describe('user and roles', () => {
it('should have inputs', () => {
component.apiRootStateResource$ = of(
createStateResource(createApiRootResource([ApiRootLinkRel.CONFIGURATION, ApiRootLinkRel.USERS])),
);
fixture.detectChanges();
const naviItemComponent: NavItemComponent = getElementComponentFromFixtureByCss<NavItemComponent>(
fixture,
usersRolesNavigationSelector,
);
expect(naviItemComponent.path).toEqual(ROUTES.BENUTZER_UND_ROLLEN);
});
it('should show if users link is present', () => { it('should show if users link is present', () => {
component.apiRootStateResource$ = of( component.apiRootStateResource$ = of(
createStateResource(createApiRootResource([ApiRootLinkRel.CONFIGURATION, ApiRootLinkRel.USERS])), createStateResource(createApiRootResource([ApiRootLinkRel.CONFIGURATION, ApiRootLinkRel.USERS])),
...@@ -389,6 +408,20 @@ describe('AppComponent', () => { ...@@ -389,6 +408,20 @@ describe('AppComponent', () => {
}); });
describe('organisationEinheiten', () => { describe('organisationEinheiten', () => {
it('should have inputs', () => {
component.apiRootStateResource$ = of(
createStateResource(createApiRootResource([ApiRootLinkRel.ORGANISATIONS_EINHEIT, ApiRootLinkRel.CONFIGURATION])),
);
fixture.detectChanges();
const naviItemComponent: NavItemComponent = getElementComponentFromFixtureByCss<NavItemComponent>(
fixture,
organisationsEinheitenNavigationSelector,
);
expect(naviItemComponent.path).toEqual(ROUTES.ORGANISATIONSEINHEITEN);
});
it('should show if link in apiRoot exists', () => { it('should show if link in apiRoot exists', () => {
component.apiRootStateResource$ = of( component.apiRootStateResource$ = of(
createStateResource(createApiRootResource([ApiRootLinkRel.ORGANISATIONS_EINHEIT, ApiRootLinkRel.CONFIGURATION])), createStateResource(createApiRootResource([ApiRootLinkRel.ORGANISATIONS_EINHEIT, ApiRootLinkRel.CONFIGURATION])),
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
import { MenuContainerComponent } from '@admin-client/configuration'; import { MenuContainerComponent } from '@admin-client/configuration';
import { ConfigurationLinkRel, ConfigurationResource, ConfigurationService } from '@admin-client/configuration-shared'; import { ConfigurationLinkRel, ConfigurationResource, ConfigurationService } from '@admin-client/configuration-shared';
import { ROUTES } from '@admin-client/shared'; import { ROUTES } from '@admin-client/shared';
import { KeycloakTokenService } from '@admin/keycloak-shared';
import { ApiRootLinkRel, ApiRootResource, ApiRootService } from '@alfa-client/api-root-shared'; import { ApiRootLinkRel, ApiRootResource, ApiRootService } from '@alfa-client/api-root-shared';
import { BuildInfoComponent } from '@alfa-client/common'; import { BuildInfoComponent } from '@alfa-client/common';
import { isLoaded, isNotUndefined, mapToResource, StateResource, TechSharedModule } from '@alfa-client/tech-shared'; import { isLoaded, isNotUndefined, mapToResource, StateResource, TechSharedModule } from '@alfa-client/tech-shared';
...@@ -32,13 +33,7 @@ import { Component, inject, OnInit } from '@angular/core'; ...@@ -32,13 +33,7 @@ import { Component, inject, OnInit } from '@angular/core';
import { Router, RouterLink, RouterOutlet } from '@angular/router'; import { Router, RouterLink, RouterOutlet } from '@angular/router';
import { AuthenticationService } from '@authentication'; import { AuthenticationService } from '@authentication';
import { hasLink } from '@ngxp/rest'; import { hasLink } from '@ngxp/rest';
import { import { AdminLogoIconComponent, NavbarComponent, NavItemComponent, OrgaUnitIconComponent, UsersIconComponent, } from '@ods/system';
AdminLogoIconComponent,
NavbarComponent,
NavItemComponent,
OrgaUnitIconComponent,
UsersIconComponent,
} from '@ods/system';
import { filter, Observable, Subscription } from 'rxjs'; import { filter, Observable, Subscription } from 'rxjs';
import { UserProfileButtonContainerComponent } from '../common/user-profile-button-container/user-profile.button-container.component'; import { UserProfileButtonContainerComponent } from '../common/user-profile-button-container/user-profile.button-container.component';
import { UnavailablePageComponent } from '../pages/unavailable/unavailable-page/unavailable-page.component'; import { UnavailablePageComponent } from '../pages/unavailable/unavailable-page/unavailable-page.component';
...@@ -71,6 +66,7 @@ export class AppComponent implements OnInit { ...@@ -71,6 +66,7 @@ export class AppComponent implements OnInit {
private readonly apiRootService = inject(ApiRootService); private readonly apiRootService = inject(ApiRootService);
private readonly router = inject(Router); private readonly router = inject(Router);
private readonly configurationService = inject(ConfigurationService); private readonly configurationService = inject(ConfigurationService);
private readonly keycloakTokenService = inject(KeycloakTokenService);
public apiRootStateResource$: Observable<StateResource<ApiRootResource>>; public apiRootStateResource$: Observable<StateResource<ApiRootResource>>;
...@@ -78,6 +74,7 @@ export class AppComponent implements OnInit { ...@@ -78,6 +74,7 @@ export class AppComponent implements OnInit {
configurationSubscription: Subscription; configurationSubscription: Subscription;
public readonly apiRootLinkRel = ApiRootLinkRel; public readonly apiRootLinkRel = ApiRootLinkRel;
public readonly routes = ROUTES;
ngOnInit(): void { ngOnInit(): void {
this.authenticationService.login().then(() => this.doAfterLoggedIn()); this.authenticationService.login().then(() => this.doAfterLoggedIn());
...@@ -85,6 +82,7 @@ export class AppComponent implements OnInit { ...@@ -85,6 +82,7 @@ export class AppComponent implements OnInit {
doAfterLoggedIn(): void { doAfterLoggedIn(): void {
this.apiRootStateResource$ = this.apiRootService.getApiRoot(); this.apiRootStateResource$ = this.apiRootService.getApiRoot();
this.keycloakTokenService.registerAccessTokenProvider();
this.evaluateInitialNavigation(); this.evaluateInitialNavigation();
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment