diff --git a/.github/integration-test/docker-compose.yml b/.github/integration-test/docker-compose.yml index f4320240..95388135 100644 --- a/.github/integration-test/docker-compose.yml +++ b/.github/integration-test/docker-compose.yml @@ -1,12 +1,12 @@ version: '3.7' services: - feasibility-gui-backend: - container_name: feasibility-gui-backend + dataportal-backend: + container_name: dataportal-backend image: backend:latest ports: - "8091:8090" depends_on: - - feasibility-gui-backend-db + - dataportal-postgres environment: QUERYRESULT_PUBLIC_KEY: "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA1lWOfXzE/mUEPitNLxsDMtjERJGVhS8gP1WmuHPvjPxUOQyod4EbJcbJlkBqLqpaIs8Buy3gcbJvIPERdG1N1BSZ8NOKOtRubioKf30JwnLdZAae3vJAzRC3h42OPM3fohZCXMxbrju+KM0ZUIrLEXKEDMHQWfevCQCxeixvXVYpfXlkJIBGaWz4cDgEOiiwhU87AMzGZwjAIHvr4oTF/uHg6+C3Mdx0m8WLtygTiEixJegMb/txR+4gNVYrzpm5BwDUU7Qxy3nTUDYZLlTGeP9MBFWW+W87IHzgP+OFr3ZKMEkAPU0R1lqXFZCYcgZHGA5He2W701isnqkKIQT8ePOH43ZOXo3S34Pqw5oQ4Q2kPubp1wgZWw0VtEiZDtlwqUJ+r3CigU7NAFM5JnC/skiIBKetbWoNm1JPEfGOTrgjHD2uo82jSO8tV45LNH1EaR2+5UWSFZyDvTayLZsxsVlRFXJKgQJDI344R6lhGbLXbhqCuPzeQaHr1XGCKAtdAgMBAAE=" # ----- app @@ -18,18 +18,19 @@ services: QUERYRESULT_EXPIRY_MINUTES: 5 ELASTIC_SEARCH_ENABLED: "false" # ---- db config - FEASIBILITY_DATABASE_HOST: "feasibility-gui-backend-db" - FEASIBILITY_DATABASE_PORT: 5432 - FEASIBILITY_DATABASE_USER: "guidbuser" - FEASIBILITY_DATABASE_PASSWORD: "guidbpw" + DATABASE_HOST: "dataportal-postgres" + DATABASE_PORT: 5432 + DATABASE_USER: "dataportaluser" + DATABASE_PASSWORD: "dataportalpw" + DATABASE_DBNAME: "dataportal" # ---- auth KEYCLOAK_ENABLED: "true" - KEYCLOAK_ALLOWED_ROLE: "FeasibilityUser" - KEYCLOAK_POWER_ROLE: "FeasibilityPowerUser" - KEYCLOAK_ADMIN_ROLE: "FeasibilityAdmin" + KEYCLOAK_ALLOWED_ROLE: "DataportalUser" + KEYCLOAK_POWER_ROLE: "DataportalPowerUser" + KEYCLOAK_ADMIN_ROLE: "DataportalAdmin" KEYCLOAK_BASE_URL_ISSUER: "http://auth:8080/auth" KEYCLOAK_BASE_URL_JWK: "http://auth:8080/auth" - KEYCLOAK_REALM: "feasibility" + KEYCLOAK_REALM: "dataportal" #---- Direct broker BROKER_CLIENT_DIRECT_ENABLED: "true" BROKER_CLIENT_DIRECT_USE_CQL: "false" @@ -42,8 +43,8 @@ services: AKTIN_BROKER_API_KEY: "xxxApiKeyAdmin123" # ---- DSF broker BROKER_CLIENT_DSF_ENABLED: "false" - DSF_SECURITY_CACERT: "/opt/codex-feasibility-security/ca.pem" - DSF_SECURITY_KEYSTORE_P12FILE: "/opt/codex-feasibility-security/test-user.p12" + DSF_SECURITY_CACERT: "/opt/dataportal-security/ca.pem" + DSF_SECURITY_KEYSTORE_P12FILE: "/opt/dataportal-security/test-user.p12" DSF_SECURITY_KEYSTORE_PASSWORD: "password" DSF_WEBSERVICE_BASE_URL: "https://dsf-zars-fhir-proxy/fhir" DSF_WEBSOCKET_URL: "wss://dsf-zars-fhir-proxy:443/fhir/ws" @@ -64,25 +65,25 @@ services: LOG_LEVEL: "warn" restart: unless-stopped volumes: - - ./ontology/ui_profiles:/opt/codex-feasibility-backend/ontology/ui_profiles - - ./ontology/codex-code-tree.json:/opt/codex-feasibility-backend/ontology/codex-code-tree.json - - ./ontology/codex-term-code-mapping.json:/opt/codex-feasibility-backend/ontology/codex-term-code-mapping.json - - ./ontology/terminology_systems.json:/opt/codex-feasibility-backend/ontology/terminology_systems.json - - ./ontology/dse/profile_tree.json:/opt/codex-feasibility-backend/ontology/dse/profile_tree.json - - ./ontology/migration/R__Load_latest_ui_profile.sql:/opt/codex-feasibility-backend/ontology/migration/R__Load_latest_ui_profile.sql - - ./secrets:/opt/codex-feasibility-security - feasibility-gui-backend-db: - image: 'postgres:15-alpine' - container_name: feasibility-gui-backend-db + - ./ontology/ui_profiles:/opt/dataportal-backend/ontology/ui_profiles + - ./ontology/codex-code-tree.json:/opt/dataportal-backend/ontology/codex-code-tree.json + - ./ontology/codex-term-code-mapping.json:/opt/dataportal-backend/ontology/codex-term-code-mapping.json + - ./ontology/terminology_systems.json:/opt/dataportal-backend/ontology/terminology_systems.json + - ./ontology/dse/profile_tree.json:/opt/dataportal-backend/ontology/dse/profile_tree.json + - ./ontology/migration/R__Load_latest_ui_profile.sql:/opt/dataportal-backend/ontology/migration/R__Load_latest_ui_profile.sql + - ./secrets:/opt/dataportal-security + dataportal-postgres: + image: 'postgres:16-alpine' + container_name: dataportal-postgres ports: - "5432:5432" environment: - POSTGRES_USER: "guidbuser" - POSTGRES_PASSWORD: "guidbpw" - POSTGRES_DB: "codex_ui" + POSTGRES_USER: "dataportaluser" + POSTGRES_PASSWORD: "dataportalpw" + POSTGRES_DB: "dataportal" blaze: - image: "samply/blaze:0.27" + image: "samply/blaze:0.29" environment: BASE_URL: "http://blaze:8080" JAVA_TOOL_OPTIONS: "-Xmx1g" @@ -103,14 +104,14 @@ services: restart: unless-stopped auth-db: - image: postgres:15-alpine + image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: "keycloakdb" POSTGRES_USER: "keycloakdbuser" POSTGRES_PASSWORD: "keycloakdbpw" volumes: - - "feasibility-auth-db:/var/lib/postgresql/data" + - "dataportal-auth-db:/var/lib/postgresql/data" auth: image: keycloak/keycloak:21.1 @@ -131,10 +132,10 @@ services: ports: - "8083:8080" volumes: - - ./keycloak-init/feasibility-realm.json:/opt/keycloak/data/import/realm.json:ro + - ./keycloak-init/dataportal-realm.json:/opt/keycloak/data/import/realm.json:ro depends_on: - auth-db volumes: - feasibility-auth-db: + dataportal-auth-db: blaze-data: diff --git a/.github/integration-test/keycloak-init/feasibility-realm.json b/.github/integration-test/keycloak-init/dataportal-realm.json similarity index 98% rename from .github/integration-test/keycloak-init/feasibility-realm.json rename to .github/integration-test/keycloak-init/dataportal-realm.json index 8aabdf9b..9193047b 100644 --- a/.github/integration-test/keycloak-init/feasibility-realm.json +++ b/.github/integration-test/keycloak-init/dataportal-realm.json @@ -1,6 +1,6 @@ { "id": "2268ed68-686a-4c9e-8637-6c366a60d459", - "realm": "feasibility", + "realm": "dataportal", "notBefore": 0, "defaultSignatureAlgorithm": "RS256", "revokeRefreshToken": false, @@ -47,8 +47,8 @@ "realm": [ { "id": "51cb7b69-d269-4872-b832-2d326e2e8658", - "name": "FeasibilityUser", - "description": "Standard feasibility user, which is allowed to send feasibility queries but has no admin rights", + "name": "DataportalUser", + "description": "Standard dataportal user, which is allowed to send dataportal queries but has no admin rights", "composite": false, "clientRole": false, "containerId": "2268ed68-686a-4c9e-8637-6c366a60d459", @@ -65,7 +65,7 @@ }, { "id": "79573cca-5524-4642-a46f-ce7b988690ae", - "name": "FeasibilityPowerUser", + "name": "DataportalPowerUser", "description": "Users in this role are not subject to the hard limit for creating queries.", "composite": false, "clientRole": false, @@ -83,7 +83,7 @@ }, { "id": "e3102b96-039a-4b74-8b7b-1ee58bac24c4", - "name": "default-roles-feasibility", + "name": "default-roles-dataportal", "description": "${role_default-roles}", "composite": true, "composites": { @@ -104,7 +104,7 @@ }, { "id": "c9d91f7e-30f0-4fab-9c06-75edad9a7fb9", - "name": "FeasibilityAdmin", + "name": "DataportalAdmin", "description": "Admin user who may access endpoints a normal user must not access. Users in this role are not subject to any rate limiting", "composite": false, "clientRole": false, @@ -113,7 +113,7 @@ } ], "client": { - "feasibility-webapp": [], + "dataportal-webapp": [], "realm-management": [ { "id": "9331cbb4-1e36-4777-9df2-e9f55541f23c", @@ -433,7 +433,7 @@ "groups": [], "defaultRole": { "id": "e3102b96-039a-4b74-8b7b-1ee58bac24c4", - "name": "default-roles-feasibility", + "name": "default-roles-dataportal", "description": "${role_default-roles}", "composite": true, "clientRole": false, @@ -504,13 +504,13 @@ "clientId": "account", "name": "${client_account}", "rootUrl": "${authBaseUrl}", - "baseUrl": "/realms/feasibility/account/", + "baseUrl": "/realms/dataportal/account/", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "redirectUris": [ - "/realms/feasibility/account/*" + "/realms/dataportal/account/*" ], "webOrigins": [], "notBefore": 0, @@ -548,13 +548,13 @@ "clientId": "account-console", "name": "${client_account-console}", "rootUrl": "${authBaseUrl}", - "baseUrl": "/realms/feasibility/account/", + "baseUrl": "/realms/dataportal/account/", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "redirectUris": [ - "/realms/feasibility/account/*" + "/realms/dataportal/account/*" ], "webOrigins": [], "notBefore": 0, @@ -680,8 +680,8 @@ }, { "id": "2bb006d9-c6d1-4694-91bb-d20c72f97ab4", - "clientId": "feasibility-webapp", - "name": "feasibility-webapp", + "clientId": "dataportal-webapp", + "name": "dataportal-webapp", "description": "", "rootUrl": "http://localhost:8091", "adminUrl": "http://localhost:8091", @@ -776,13 +776,13 @@ "clientId": "security-admin-console", "name": "${client_security-admin-console}", "rootUrl": "${authAdminUrl}", - "baseUrl": "/admin/feasibility/console/", + "baseUrl": "/admin/dataportal/console/", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "redirectUris": [ - "/admin/feasibility/console/*" + "/admin/dataportal/console/*" ], "webOrigins": [ "+" diff --git a/.github/integration-test/ontology/codex-code-tree.json b/.github/integration-test/ontology/codex-code-tree.json deleted file mode 100644 index b8973e47..00000000 --- a/.github/integration-test/ontology/codex-code-tree.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "children": [ - { - "children": [ - { - "termCode": { - "code": "gender", - "display": "Geschlecht", - "system": "mii.abide" - } - } - ], - "termCode": { - "code": "PatientIn", - "display": "PatientIn", - "system": "mii.abide" - } - } - ], - "termCode": { - "code": "", - "display": "", - "system": "" - } -} diff --git a/.github/integration-test/ontology/codex-term-code-mapping.json b/.github/integration-test/ontology/codex-term-code-mapping.json deleted file mode 100644 index e3cf057b..00000000 --- a/.github/integration-test/ontology/codex-term-code-mapping.json +++ /dev/null @@ -1,23 +0,0 @@ -[ - { - "attributeSearchParameters": [ - { - "attributeFhirPath": "gender", - "attributeKey": { - "code": "gender", - "display": "Geschlecht", - "system": "mii.abide" - }, - "attributeSearchParameter": "gender", - "attributeType": "code" - } - ], - "fhirResourceType": "Patient", - "key": { - "code": "gender", - "display": "Geschlecht", - "system": "mii.abide" - } - } -] - diff --git a/.github/scripts/check-if-running-as-dataportal-user.sh b/.github/scripts/check-if-running-as-dataportal-user.sh new file mode 100755 index 00000000..8b508fc6 --- /dev/null +++ b/.github/scripts/check-if-running-as-dataportal-user.sh @@ -0,0 +1,10 @@ +#!/bin/bash -e + +if docker exec -u0 dataportal-backend pgrep -u dataportal java > /dev/null +then + echo "Java process is running as dataportal" + exit 0 +else + echo "Java process is not running as dataportal" + exit 1 +fi diff --git a/.github/scripts/check-if-running-as-feasibility-user.sh b/.github/scripts/check-if-running-as-feasibility-user.sh deleted file mode 100755 index ef458a1f..00000000 --- a/.github/scripts/check-if-running-as-feasibility-user.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -e - -if docker exec -u0 feasibility-gui-backend pgrep -u feasibility java > /dev/null -then - echo "Java process is running as feasibility" - exit 0 -else - echo "Java process is not running as feasibility" - exit 1 -fi diff --git a/.github/scripts/create-keycloak-user.sh b/.github/scripts/create-keycloak-user.sh index 2f8344d5..4a416d89 100755 --- a/.github/scripts/create-keycloak-user.sh +++ b/.github/scripts/create-keycloak-user.sh @@ -1,6 +1,6 @@ #!/bin/bash -e docker exec -u0 integration-test-auth-1 /opt/keycloak/bin/kcadm.sh config credentials --server http://localhost:8080/auth --realm master --user keycloakadmin --password keycloak -docker exec -u0 integration-test-auth-1 /opt/keycloak/bin/kcadm.sh create users -s username=testuser -s email=test@example.com -s enabled=true -s emailVerified=true -r feasibility -docker exec -u0 integration-test-auth-1 /opt/keycloak/bin/kcadm.sh add-roles --uusername testuser --rolename FeasibilityUser -r feasibility -docker exec -u0 integration-test-auth-1 /opt/keycloak/bin/kcadm.sh set-password -r feasibility --username testuser --new-password testpassword +docker exec -u0 integration-test-auth-1 /opt/keycloak/bin/kcadm.sh create users -s username=testuser -s email=test@example.com -s enabled=true -s emailVerified=true -r dataportal +docker exec -u0 integration-test-auth-1 /opt/keycloak/bin/kcadm.sh add-roles --uusername testuser --rolename DataportalUser -r dataportal +docker exec -u0 integration-test-auth-1 /opt/keycloak/bin/kcadm.sh set-password -r dataportal --username testuser --new-password testpassword diff --git a/.github/scripts/download-and-unpack-ontology.sh b/.github/scripts/download-and-unpack-ontology.sh index 15b6767f..63cdba93 100755 --- a/.github/scripts/download-and-unpack-ontology.sh +++ b/.github/scripts/download-and-unpack-ontology.sh @@ -1,7 +1,7 @@ #!/bin/bash -e mkdir --parents .github/integration-test/ontology/ui_profiles .github/integration-test/ontology/migration -curl -L https://github.com/medizininformatik-initiative/fhir-ontology-generator/raw/v2.0.1-RC/example/mii_core_data_set/ontology/backend.zip -o .github/integration-test/ontology/backend.zip +curl -L https://github.com/medizininformatik-initiative/fhir-ontology-generator/raw/v3.0.0-test.1/example/fdpg-ontology/backend.zip -o .github/integration-test/ontology/backend.zip unzip -jod .github/integration-test/ontology/ui_profiles/ .github/integration-test/ontology/backend.zip mv .github/integration-test/ontology/ui_profiles/R__Load_latest_ui_profile.sql .github/integration-test/ontology/migration/ rm .github/integration-test/ontology/backend.zip diff --git a/.github/scripts/post-test-query.sh b/.github/scripts/post-test-query.sh index 7c17bde0..d82fde5f 100755 --- a/.github/scripts/post-test-query.sh +++ b/.github/scripts/post-test-query.sh @@ -1,16 +1,16 @@ #!/bin/bash -e access_token="$(curl -s --request POST \ - --url http://localhost:8083/auth/realms/feasibility/protocol/openid-connect/token \ + --url http://localhost:8083/auth/realms/dataportal/protocol/openid-connect/token \ --header 'Content-Type: application/x-www-form-urlencoded' \ --data grant_type=password \ - --data client_id=feasibility-webapp \ + --data client_id=dataportal-webapp \ --data username=testuser \ --data password=testpassword \ --data scope=openid | jq '.access_token' | tr -d '"')" response=$(curl -s -i \ - --url http://localhost:8091/api/v3/query \ + --url http://localhost:8091/api/v4/query \ --header "Authorization: Bearer $access_token" \ --header 'Content-Type: application/json' \ --data '{ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c9b97b6..63735242 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,11 +53,11 @@ jobs: - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 - - name: Upload Feasibility Backend Jar + - name: Upload Dataportal Backend Jar uses: actions/upload-artifact@v4 with: name: backend-jar - path: target/feasibilityBackend.jar + path: target/dataportalBackend.jar - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -67,13 +67,13 @@ jobs: with: context: . tags: backend:latest - outputs: type=docker,dest=/tmp/feasibilityBackend.tar + outputs: type=docker,dest=/tmp/dataportalBackend.tar - - name: Upload Feasibility Backend Image + - name: Upload Dataportal Backend Image uses: actions/upload-artifact@v4 with: name: backend-image - path: /tmp/feasibilityBackend.tar + path: /tmp/dataportalBackend.tar security-scan: runs-on: ubuntu-latest @@ -129,7 +129,7 @@ jobs: - name: Check out Git repository uses: actions/checkout@v4 - - name: Download Feasibility Backend Image + - name: Download Dataportal Backend Image uses: actions/download-artifact@v4 with: name: backend-image @@ -138,20 +138,20 @@ jobs: - name: Install jq uses: dcarbone/install-jq-action@v1.0.1 - - name: Load Feasibility Backend Image - run: docker load --input /tmp/feasibilityBackend.tar + - name: Load Dataportal Backend Image + run: docker load --input /tmp/dataportalBackend.tar - name: Download ontology files from github run: .github/scripts/download-and-unpack-ontology.sh - - name: Run Feasibility Backend with Database, Keycloak and Blaze + - name: Run Dataportal Backend with Database, Keycloak and Blaze run: docker compose -f .github/integration-test/docker-compose.yml up -d - - name: Wait for Feasibility Backend + - name: Wait for Dataportal Backend run: .github/scripts/wait-for-url.sh http://localhost:8091/actuator/health - - name: Check if Feasibility Backend is correctly running with the feasibility user - run: .github/scripts/check-if-running-as-feasibility-user.sh + - name: Check if Dataportal Backend is correctly running with the dataportal user + run: .github/scripts/check-if-running-as-dataportal-user.sh - name: Wait for Blaze run: .github/scripts/wait-for-url.sh http://localhost:8082/health diff --git a/CHANGELOG.md b/CHANGELOG.md index 1887838f..32f2ca2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +## [6.0.0-alpha.2] - 2024-09-04 + +### Added +- JVM options configurable in Dockerimage +### Changed +- moved rest api from v3 to v4 +- codex and or feasibility references are replaced by dataportal (not in package names though) +- openapi documentation updated to 3.1.0 +### Removed +- Unused endpoints from the /terminology path + ## [6.0.0-alpha.1] - 2024-09-02 ### Added diff --git a/Dockerfile b/Dockerfile index ebfee2ec..c6871383 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,28 +3,28 @@ FROM eclipse-temurin:17-jre RUN apt update -yqq && apt upgrade -yqq && \ apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/ -WORKDIR /opt/codex-feasibility-backend -COPY ./target/*.jar ./feasibility-gui-backend.jar +WORKDIR /opt/dataportal-backend +COPY ./target/*.jar ./dataportal-backend.jar COPY ontology ontology -RUN groupadd --system feasibility && useradd --system feasibility -g feasibility +RUN groupadd --system dataportal && useradd --system dataportal -g dataportal RUN mkdir logging -RUN chown -R feasibility:feasibility /opt/codex-feasibility-backend +RUN chown -R dataportal:dataportal /opt/dataportal-backend -USER feasibility:feasibility +USER dataportal:dataportal -ARG VERSION=2.1.0 +ARG VERSION=6.0.0 ENV APP_VERSION=${VERSION} -ENV FEASIBILITY_DATABASE_HOST="feasibility-network" -ENV FEASIBILITY_DATABASE_PORT=5432 -ENV FEASIBILITY_DATABASE_USER=postgres -ENV FEASIBILITY_DATABASE_PASSWORD=password -ENV CERTIFICATE_PATH=/opt/codex-feasibility-backend/certs -ENV TRUSTSTORE_PATH=/opt/codex-feasibility-backend/truststore +ENV DATABASE_HOST="dataportal-network" +ENV DATABASE_PORT=5432 +ENV DATABASE_USER=postgres +ENV DATABASE_PASSWORD=password +ENV CERTIFICATE_PATH=/opt/dataportal-backend/certs +ENV TRUSTSTORE_PATH=/opt/dataportal-backend/truststore ENV TRUSTSTORE_FILE=self-signed-truststore.jks RUN mkdir -p $CERTIFICATE_PATH $TRUSTSTORE_PATH -RUN chown feasibility:feasibility $CERTIFICATE_PATH $TRUSTSTORE_PATH +RUN chown dataportal:dataportal $CERTIFICATE_PATH $TRUSTSTORE_PATH HEALTHCHECK --interval=5s --start-period=10s CMD curl -s -f http://localhost:8090/actuator/health || exit 1 @@ -40,5 +40,5 @@ LABEL maintainer="medizininformatik-initiative" \ org.opencontainers.image.version=${VERSION} \ org.opencontainers.image.revision=${GIT_REF} \ org.opencontainers.image.vendor="medizininformatik-initiative" \ - org.opencontainers.image.title="feasibility backend" \ - org.opencontainers.image.description="Provides backend functions for feasibility UI including query execution" + org.opencontainers.image.title="dataportal backend" \ + org.opencontainers.image.description="Provides backend functions for the dataportal" diff --git a/README.md b/README.md index 8205fd99..b8aee440 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,41 @@ -# MII Feasibility Backend +# Dataportal Backend ## Configuration Base -| EnvVar | Description | Example | Default | -|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|--------------------------------------------------| -| LOG_LEVEL | Sets the log level being used. Possible values are: `error`, `warn`, `info`, `debug` and `trace`. | | `warn` | -| HIBERNATE_SHOW_SQL | Show the sql statements hibernate executes | | `false` | -| BROKER_CLIENT_MOCK_ENABLED | Enables the mock client. Possible values are `true` and `false`. | | `true` | -| BROKER_CLIENT_DIRECT_ENABLED | Enables the direct client. Possible values are `true` and `false`. | | `false` | -| BROKER_CLIENT_AKTIN_ENABLED | Enables the aktin client. Possible values are `true` and `false`. | | `false` | -| BROKER_CLIENT_DSF_ENABLED | Enables the dsf client. Possible values are `true` and `false`. | | `false` | -| KEYCLOAK_BASE_URL | Base URL of the keycloak instance. | | `http://localhost:8080` | -| KEYCLOAK_BASE_URL_ISSUER | Base URL the keycloak instance uses in the issuer claim | | `http://localhost:8080` | -| KEYCLOAK_BASE_URL_JWK | Base URL for the JWK Set URI of the keycloak instance | | `http://localhost:8080` | -| KEYCLOAK_REALM | Realm to be used for checking bearer tokens. | | `feasibility` | -| KEYCLOAK_CLIENT_ID | Client ID to be used for checking bearer tokens. | | `feasibility-webapp` | -| KEYCLOAK_ALLOWED_ROLE | Role that has to be part of the bearer token in order for the requester to be authorized. | | `FeasibilityUser` | -| KEYCLOAK_POWER_ROLE | Optional role that can be assigned to a user to free them from being subject to any hard limits (see _PRIVACY_QUOTA_HARD.*_ EnvVars). | | `FeasibilityPowerUser` | -| KEYCLOAK_ADMIN_ROLE | Role that gives admin rights to a user. Admins do not fall under any limits and can also see un-obfuscated site names. | | `FeasibilityAdmin` | -| SPRING_DATASOURCE_URL | The JDBC URL of the Postgres feasibility database. | | `jdbc:postgresql://feasibility-db:5432/codex_ui` | -| SPRING_DATASOURCE_USERNAME | Username to connect to the Postgres feasibility database. | | `guidbuser` | -| SPRING_DATASOURCE_PASSWORD | Password to connect to the Postgres feasibility database. | | `guidbpw` | -| ONTOLOGY_FILES_FOLDER_UI | | | ontology/ui_profiles | -| ONTOLOGY_DB_MIGRATION_FOLDER | | | ontology/migration | -| MAPPINGS_FILE | | | ontology/termCodeMapping.json | -| CONCEPT_TREE_FILE | | | ontology/conceptTree.json | -| CQL_TRANSLATE_ENABLED | | | true | -| FHIR_TRANSLATE_ENABLED | | | false | -| FLARE_WEBSERVICE_BASE_URL | URL of the local FLARE webservice - needed for FHIR query translation and when running the DIRECT path | | http://localhost:5000 | -| CQL_SERVER_BASE_URL | URL of the local FHIR server that handles CQL requests | | http://cql | -| API_BASE_URL | Sets the base URL of the webservice. This is necessary if the webservice is running behind a proxy server. If not filled, the API base URL is the request URL | https://host/api | | -| QUERY_VALIDATION_ENABLED | When enabled, any structured query submitted via the `run-query` endpoint is validated against the JSON schema located in `src/main/resources/query/query-schema.json` | true / false | true | -| QUERYRESULT_EXPIRY_MINUTES | How many minutes should query results be kept in memory? | | 5 | +| EnvVar | Description | Example | Default | +|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|---------------------------------------------------| +| LOG_LEVEL | Sets the log level being used. Possible values are: `error`, `warn`, `info`, `debug` and `trace`. | | `warn` | +| HIBERNATE_SHOW_SQL | Show the sql statements hibernate executes | | `false` | +| BROKER_CLIENT_MOCK_ENABLED | Enables the mock client. Possible values are `true` and `false`. | | `true` | +| BROKER_CLIENT_DIRECT_ENABLED | Enables the direct client. Possible values are `true` and `false`. | | `false` | +| BROKER_CLIENT_AKTIN_ENABLED | Enables the aktin client. Possible values are `true` and `false`. | | `false` | +| BROKER_CLIENT_DSF_ENABLED | Enables the dsf client. Possible values are `true` and `false`. | | `false` | +| KEYCLOAK_BASE_URL | Base URL of the keycloak instance. | | `http://localhost:8080` | +| KEYCLOAK_BASE_URL_ISSUER | Base URL the keycloak instance uses in the issuer claim | | `http://localhost:8080` | +| KEYCLOAK_BASE_URL_JWK | Base URL for the JWK Set URI of the keycloak instance | | `http://localhost:8080` | +| KEYCLOAK_REALM | Realm to be used for checking bearer tokens. | | `dataportal` | +| KEYCLOAK_CLIENT_ID | Client ID to be used for checking bearer tokens. | | `dataportal-webapp` | +| KEYCLOAK_ALLOWED_ROLE | Role that has to be part of the bearer token in order for the requester to be authorized. | | `DataportalUser` | +| KEYCLOAK_POWER_ROLE | Optional role that can be assigned to a user to free them from being subject to any hard limits (see _PRIVACY_QUOTA_HARD.*_ EnvVars). | | `DataportalPowerUser` | +| KEYCLOAK_ADMIN_ROLE | Role that gives admin rights to a user. Admins do not fall under any limits and can also see un-obfuscated site names. | | `DataportalAdmin` | +| SPRING_DATASOURCE_URL | The JDBC URL of the Postgres dataportal database. | | `jdbc:postgresql://dataportal-db:5432/dataportal` | +| SPRING_DATASOURCE_USERNAME | Username to connect to the Postgres dataportal database. | | `dataportaluser` | +| SPRING_DATASOURCE_PASSWORD | Password to connect to the Postgres dataportal database. | | `dataportalpw` | +| ONTOLOGY_DB_MIGRATION_FOLDER | | | ontology/migration | +| MAPPINGS_FILE | | | ontology/termCodeMapping.json | +| CONCEPT_TREE_FILE | | | ontology/conceptTree.json | +| CQL_TRANSLATE_ENABLED | | | true | +| FHIR_TRANSLATE_ENABLED | | | false | +| FLARE_WEBSERVICE_BASE_URL | URL of the local FLARE webservice - needed for FHIR query translation and when running the DIRECT path | | http://localhost:5000 | +| CQL_SERVER_BASE_URL | URL of the local FHIR server that handles CQL requests | | http://cql | +| API_BASE_URL | Sets the base URL of the webservice. This is necessary if the webservice is running behind a proxy server. If not filled, the API base URL is the request URL | https://host/api | | +| QUERY_VALIDATION_ENABLED | When enabled, any structured query submitted via the `run-query` endpoint is validated against the JSON schema located in `src/main/resources/query/query-schema.json` | true / false | true | +| QUERYRESULT_EXPIRY_MINUTES | How many minutes should query results be kept in memory? | | 5 | | QUERYRESULT_PUBLIC_KEY | The public key in Base64-encoded DER format without banners and line breaks. Mandatory if _QUERYRESULT_DISABLE_LOG_FILE_ENCRYPTION_ is _false_ | -| QUERYRESULT_DISABLE_LOG_FILE_ENCRYPTION | Disable encryption of the result log file. | true / false | | -| ALLOWED_ORIGINS | Allowed origins for cross-origin requests. This should at least cover the frontend address. | | http://localhost | -| MAX_SAVED_QUERIES_PER_USER | How many slots does a user have to store saved queries. | | 10 | +| QUERYRESULT_DISABLE_LOG_FILE_ENCRYPTION | Disable encryption of the result log file. | true / false | | +| ALLOWED_ORIGINS | Allowed origins for cross-origin requests. This should at least cover the frontend address. | | http://localhost | +| MAX_SAVED_QUERIES_PER_USER | How many slots does a user have to store saved queries. | | 10 | ### Running the DIRECT Path @@ -47,7 +46,7 @@ handles obfuscation by adding or subtracting a random number <=5. | EnvVar | Description | Example | Default | |-----------------------------------------------|-------------------------------------------------------------------------------------------------|-------------------------------------|---------| -| BROKER_CLIENT_DIRECT_AUTH_BASIC_USERNAME | Username to use to connect to flare or directly to the FHIR server via CQL | feas-user | | +| BROKER_CLIENT_DIRECT_AUTH_BASIC_USERNAME | Username to use to connect to flare or directly to the FHIR server via CQL | dataportal-user | | | BROKER_CLIENT_DIRECT_AUTH_BASIC_PASSWORD | Password for that user | verysecurepassword | | | BROKER_CLIENT_DIRECT_AUTH_OAUTH_ISSUER_URL | Issuer URL of OpenID Connect provider for authenticating access to OAuth2 protected FHIR server | https://auth.example.com/realms/foo | | | BROKER_CLIENT_DIRECT_AUTH_OAUTH_CLIENT_ID | Client ID to use when authenticating at OpenID Connect provider | foo_client | | @@ -83,7 +82,6 @@ When using API-key authentication, please make sure that the broker server has a corresponding API-key entry with `OU=admin` contained in the DN-string. - ### Running the DSF Path In order to run the backend using the DSF path, the following environment variables need to be set: @@ -143,8 +141,8 @@ In order to configure the location of the external service, use the following pa ## Support for self-signed certificates -The feasibility backend supports the use of self-signed certificates from your own CAs. -On each startup, the feasibility backend will search through the folder /app/certs inside the container, add all found +The dataportal backend supports the use of self-signed certificates from your own CAs. +On each startup, the dataportal backend will search through the folder /app/certs inside the container, add all found CA *.pem files to a java truststore and start the application with this truststore. Using docker-compose, mount a folder from your host (e.g.: ./certs) to the /app/certs folder, @@ -162,7 +160,7 @@ In order to run this project the following steps need to be followed: ### Adding GitHub Package Repositories -This project uses dependencies ([HiGHmed DSF](https://github.com/highmed/highmed-dsf) and [sq2cql](https://github.com/medizininformatik-initiative/sq2cql)) which are not hosted on maven central but on GitHub. +This project uses dependencies ([sq2cql](https://github.com/medizininformatik-initiative/sq2cql)) which are not hosted on maven central but on GitHub. In order to download artifacts from GitHub package repositories, you need to add your GitHub login credentials to your central maven config file. @@ -179,12 +177,7 @@ After that, add the following `` configurations to the `` secti http://maven.apache.org/xsd/settings-1.0.0.xsd"> - codex - USERNAME - TOKEN - - - highmed + mii USERNAME TOKEN @@ -221,7 +214,7 @@ One can then connect to the same database when starting the backend in an IDE. ## Working with the Backend -This backend provides a rest webservice which connects the [MII feasibility gui](https://github.com/medizininformatik-initiative/feasibility-gui) +This backend provides a rest webservice which connects the [Dataportal GUI](https://github.com/medizininformatik-initiative/feasibility-gui) and the corresponding middlewares. To send a query to the backend, use the following example query: @@ -290,7 +283,7 @@ or at http://localhost:8090/swagger-ui/index.html when running) ### Creating the Docker Image ``` mvn install -docker build -t feasibility-gui-backend . +docker build -t dataportal-backend . ``` ### Starting the Backend and the Database @@ -300,12 +293,12 @@ docker-compose up -d **Note:** _If you need the database to run using another port than 5432 then set the corresponding environment variable like:_ ``` -FEASIBILITY_DATABASE_PORT= docker-compose up -d +DATAPORTAL_DATABASE_PORT= docker-compose up -d ``` ### Testing if the Container is Running Properly ``` -GET http://localhost:8090/api/v3/terminology/root-entries +GET http://localhost:8090/actuator/health ``` Should reply with status 200 and a JSON object diff --git a/docker-compose.yml b/docker-compose.yml index e08148e4..8468c98f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,103 +1,119 @@ -version: '3.7' services: - feasibility-gui-backend: - container_name: feasibility-gui-backend + dataportal-backend: + container_name: dataportal-backend + restart: unless-stopped build: . ports: - - ${CODEX_FEASIBILITY_BACKEND_PORT:-127.0.0.1:8091}:8090 + - ${DATAPORTAL_BACKEND_PORT:-127.0.0.1:8091}:8090 depends_on: - feasibility-db: + dataportal-postgres: condition: service_started - feasibility-es: + dataportal-elastic: condition: service_healthy init-elasticsearch: condition: service_completed_successfully environment: - SPRING_DATASOURCE_URL: ${CODEX_FEASIBILITY_BACKEND_DATASOURCE_URL:-jdbc:postgresql://feasibility-db:5432/codex_ui?currentSchema=codex} - SPRING_DATASOURCE_USERNAME: ${CODEX_FEASIBILITY_BACKEND_DATASOURCE_USERNAME:-codex-postgres} - SPRING_DATASOURCE_PASSWORD: ${CODEX_FEASIBILITY_BACKEND_DATASOURCE_PASSWORD:-codex-password} - LOG_LEVEL: ${CODEX_FEASIBILITY_BACKEND_LOG_LEVEL:-warn} - BROKER_CLIENT_MOCK_ENABLED: ${CODEX_FEASIBILITY_BACKEND_BROKER_CLIENT_MOCK_ENABLED:-true} - BROKER_CLIENT_DIRECT_ENABLED: ${CODEX_FEASIBILITY_BACKEND_BROKER_CLIENT_DIRECT_ENABLED:-false} - BROKER_CLIENT_AKTIN_ENABLED: ${CODEX_FEASIBILITY_BACKEND_BROKER_CLIENT_AKTIN_ENABLED:-false} - BROKER_CLIENT_DSF_ENABLED: ${CODEX_FEASIBILITY_BACKEND_BROKER_CLIENT_DSF_ENABLED:-false} - KEYCLOAK_ENABLED: ${CODEX_FEASIBILITY_BACKEND_KEYCLOAK_ENABLED:-true} - KEYCLOAK_BASE_URL: ${CODEX_FEASIBILITY_BACKEND_KEYCLOAK_BASE_URL:-http://keycloak:8080} - KEYCLOAK_REALM: ${CODEX_FEASIBILITY_BACKEND_KEYCLOAK_REALM:-feasibility} - KEYCLOAK_BASE_URL_ISSUER: ${CODEX_FEASIBILITY_BACKEND_KEYCLOAK_BASE_URL_ISSUER:-http://localhost:8080/auth} - KEYCLOAK_BASE_URL_JWK: ${CODEX_FEASIBILITY_BACKEND_KEYCLOAK_BASE_URL_JWK:-http://localhost:8080/auth} - KEYCLOAK_CLIENT_ID: ${CODEX_FEASIBILITY_BACKEND_KEYCLOAK_CLIENT_ID:-feasibility-gui} - KEYCLOAK_ALLOWED_ROLE: ${CODEX_FEASIBILITY_BACKEND_KEYCLOAK_ALLOWED_ROLE:-CODEX_USER} - KEYCLOAK_POWER_ROLE: ${CODEX_FEASIBILITY_BACKEND_KEYCLOAK_POWER_ROLE:-CODEX_POWER_USER} - ONTOLOGY_FILES_FOLDER_UI: ${CODEX_FEASIBILITY_BACKEND_ONTOLOGY_FILES_FOLDER:-/opt/codex-feasibility-backend/ontology} - ONTOLOGY_DB_MIGRATION_FOLDER: ${CODEX_FEASIBILITY_BACKEND_ONTOLOGY_DB_MIGRATION_FOLDER:-/opt/codex-feasibility-backend/ontology/migration} - MAPPINGS_FILE: ${CODEX_FEASIBILITY_BACKEND_ONTOLOGY_FILES_FOLDER:-/opt/codex-feasibility-backend/ontology}/codex-term-code-mapping.json - CONCEPT_TREE_FILE: ${CODEX_FEASIBILITY_BACKEND_ONTOLOGY_FILES_FOLDER:-/opt/codex-feasibility-backend/ontology}/codex-code-tree.json - CQL_TRANSLATE_ENABLED: ${CODEX_FEASIBILITY_BACKEND_CQL_TRANSLATE_ENABLED:-true} - FHIR_TRANSLATE_ENABLED: ${CODEX_FEASIBILITY_BACKEND_FHIR_TRANSLATE_ENABLED:-false} - FLARE_WEBSERVICE_BASE_URL: ${CODEX_FEASIBILITY_BACKEND_FLARE_WEBSERVICE_BASE_URL:-http://flare:5000} - CQL_SERVER_BASE_URL: ${CODEX_FEASIBILITY_BACKEND_CQL_SERVER_BASE_URL:-http://cql} - API_BASE_URL: ${CODEX_FEASIBILITY_BACKEND_API_BASE_URL:-} - QUERY_VALIDATION_ENABLED: ${CODEX_FEASIBILITY_BACKEND_QUERY_VALIDATION_ENABLED:-true} - QUERYRESULT_EXPIRY_MINUTES: ${CODEX_FEASIBILITY_BACKEND_QUERYRESULT_EXPIRY_MINUTES:-5} - MAX_SAVED_QUERIES_PER_USER: ${CODEX_FEASIBILITY_BACKEND_MAX_SAVED_QUERIES_PER_USER:-10} - # ---- Direct - BROKER_CLIENT_DIRECT_AUTH_BASIC_USERNAME: ${CODEX_FEASIBILITY_BACKEND_BROKER_CLIENT_DIRECT_AUTH_BASIC_USERNAME} - BROKER_CLIENT_DIRECT_AUTH_BASIC_PASSWORD: ${CODEX_FEASIBILITY_BACKEND_BROKER_CLIENT_DIRECT_AUTH_BASIC_PASSWORD} - BROKER_CLIENT_DIRECT_USE_CQL: ${CODEX_FEASIBILITY_BACKEND_BROKER_CLIENT_DIRECT_USE_CQL:-false} - BROKER_CLIENT_OBFUSCATE_RESULT_COUNT: ${CODEX_FEASIBILITY_BACKEND_BROKER_CLIENT_OBFUSCATE_RESULT_COUNT:-false} - # ---- Aktin - AKTIN_BROKER_BASE_URL: ${CODEX_FEASIBILITY_BACKEND_AKTIN_BROKER_BASE_URL:-http://aktin:8080/broker} - AKTIN_BROKER_API_KEY: ${CODEX_FEASIBILITY_BACKEND_AKTIN_BROKER_API_KEY:-abc123} - # ---- DSF - DSF_SECURITY_CACERT: /opt/codex-feasibility-backend/dsf-security/${CODEX_FEASIBILITY_BACKEND_DSF_SECURITY_CACERT} - DSF_SECURITY_KEYSTORE_P12FILE: /opt/codex-feasibility-backend/dsf-security/${CODEX_FEASIBILITY_BACKEND_DSF_SECURITY_KEYSTORE_P12FILE} - DSF_SECURITY_KEYSTORE_PASSWORD: ${CODEX_FEASIBILITY_BACKEND_DSF_SECURITY_KEYSTORE_PASSWORD} - DSF_PROXY_HOST: ${CODEX_FEASIBILITY_BACKEND_DSF_PROXY_HOST:-} - DSF_PROXY_USERNAME: ${CODEX_FEASIBILITY_BACKEND_DSF_PROXY_USERNAME:-} - DSF_PROXY_PASSWORD: ${CODEX_FEASIBILITY_BACKEND_DSF_PROXY_PASSWORD:-} - DSF_WEBSERVICE_BASE_URL: ${CODEX_FEASIBILITY_BACKEND_DSF_WEBSERVICE_BASE_URL:-https://dsf:8080/fhir} - DSF_WEBSOCKET_URL: ${CODEX_FEASIBILITY_BACKEND_DSF_WEBSOCKET_URL:-wws://dsf:8080/fhir/ws} - DSF_ORGANIZATION_ID: ${CODEX_FEASIBILITY_BACKEND_DSF_ORGANIZATION_ID} - # ---- Privacy and Obfuscation - PRIVACY_QUOTA_SOFT_CREATE_AMOUNT: ${CODEX_FEASIBILITY_BACKEND_PRIVACY_QUOTA_SOFT_CREATE_AMOUNT} - PRIVACY_QUOTA_SOFT_CREATE_INTERVALMINUTES: ${CODEX_FEASIBILITY_BACKEND_PRIVACY_QUOTA_SOFT_CREATE_INTERVALMINUTES} - PRIVACY_QUOTA_HARD_CREATE_AMOUNT: ${CODEX_FEASIBILITY_BACKEND_PRIVACY_QUOTA_HARD_CREATE_AMOUNT} - PRIVACY_QUOTA_HARD_CREATE_INTERVALMINUTES: ${CODEX_FEASIBILITY_BACKEND_PRIVACY_QUOTA_HARD_CREATE_INTERVALMINUTES} - PRIVACY_QUOTA_READ_SUMMARY_POLLINGINTERVALSECONDS: ${CODEX_FEASIBILITY_BACKEND_PRIVACY_QUOTA_READ_SUMMARY_POLLINGINTERVALSECONDS} - PRIVACY_QUOTA_READ_DETAILED_OBFUSCATED_POLLINGINTERVALSECONDS: ${CODEX_FEASIBILITY_BACKEND_PRIVACY_QUOTA_READ_DETAILED_OBFUSCATED_POLLINGINTERVALSECONDS} - PRIVACY_QUOTA_READ_DETAILED_OBFUSCATED_AMOUNT: ${CODEX_FEASIBILITY_BACKEND_PRIVACY_QUOTA_READ_DETAILED_OBFUSCATED_AMOUNT} - PRIVACY_QUOTA_READ_DETAILED_OBFUSCATED_INTERVALSECONDS: ${CODEX_FEASIBILITY_BACKEND_PRIVACY_QUOTA_READ_DETAILED_OBFUSCATED_INTERVALSECONDS} - # If the total result for a query is below this number, return an empty result instead. - PRIVACY_THRESHOLD_RESULTS: ${CODEX_FEASIBILITY_BACKEND_PRIVACY_THRESHOLD_RESULTS} - PRIVACY_THRESHOLD_SITES: ${CODEX_FEASIBILITY_BACKEND_PRIVACY_THRESHOLD_SITES} - PRIVACY_THRESHOLD_SITES_RESULT: ${CODEX_FEASIBILITY_BACKEND_PRIVACY_THRESHOLD_SITES_RESULT} - QUERYRESULT_DISABLE_LOG_FILE_ENCRYPTION: "false" + # ----- app + QUERY_VALIDATION_ENABLED: ${DATAPORTAL_BACKEND_QUERY_VALIDATION_ENABLED:-true} + CQL_TRANSLATE_ENABLED: ${DATAPORTAL_BACKEND_CQL_TRANSLATE_ENABLED:-true} + FHIR_TRANSLATE_ENABLED: ${DATAPORTAL_BACKEND_FHIR_TRANSLATE_ENABLED:-false} + API_BASE_URL: ${DATAPORTAL_BACKEND_API_BASE_URL:-https://localhost/api/} + ALLOWED_ORIGINS: ${DATAPORTAL_BACKEND_ALLOWED_ORIGINS:-https://localhost} + QUERYRESULT_EXPIRY_MINUTES: ${DATAPORTAL_BACKEND_QUERYRESULT_EXPIRY_MINUTES:-5} + ONTOLOGY_ORDER: ${DATAPORTAL_BACKEND_ONTOLOGY_ORDER:-"Diagnose, Prozedur, Person, Laboruntersuchung, Medikamentenverabreichung, Bioprobe, Einwilligung"} + MAX_SAVED_QUERIES_PER_USER: ${DATAPORTAL_BACKEND_MAX_SAVED_QUERIES_PER_USER:-100} + # ---- db config + DATABASE_HOST: ${DATAPORTAL_BACKEND_DATABASE_HOST:-dataportal-backend-db} + DATABASE_PORT: ${DATAPORTAL_BACKEND_DATABASE_PORT:-5432} + DATABASE_USER: ${DATAPORTAL_BACKEND_DATABASE_USERNAME:-dataportaluser} + DATABASE_PASSWORD: ${DATAPORTAL_BACKEND_DATABASE_PASSWORD:-dataportalpw} + DATABASE_DBNAME: ${DATAPORTAL_BACKEND_DATABASE_DBNAME:-dataportal} + # ---- ontology + ONTOLOGY_FILES_FOLDER_UI: ${DATAPORTAL_BACKEND_ONTOLOGY_FILES_FOLDER:-/opt/dataportal-backend/ontology} + ONTOLOGY_DB_MIGRATION_FOLDER: ${DATAPORTAL_BACKEND_ONTOLOGY_DB_MIGRATION_FOLDER:-/opt/dataportal-backend/ontology/migration} + MAPPINGS_FILE: ${DATAPORTAL_BACKEND_ONTOLOGY_FILES_FOLDER:-/opt/dataportal-backend/ontology}/codex-term-code-mapping.json + CONCEPT_TREE_FILE: ${DATAPORTAL_BACKEND_ONTOLOGY_FILES_FOLDER:-/opt/dataportal-backend/ontology}/codex-code-tree.json + # ---- auth + KEYCLOAK_ENABLED: ${DATAPORTAL_BACKEND_KEYCLOAK_ENABLED:-true} + KEYCLOAK_BASE_URL: ${DATAPORTAL_BACKEND_KEYCLOAK_BASE_URL:-http://keycloak:8080} + KEYCLOAK_CLIENT_ID: ${DATAPORTAL_BACKEND_KEYCLOAK_CLIENT_ID:-dataportal-gui} + KEYCLOAK_ALLOWED_ROLE: ${DATAPORTAL_BACKEND_KEYCLOAK_ALLOWED_ROLE:-DataportalUser} + KEYCLOAK_POWER_ROLE: ${DATAPORTAL_BACKEND_KEYCLOAK_POWER_ROLE:-DataportalPowerUser} + KEYCLOAK_ADMIN_ROLE: ${DATAPORTAL_BACKEND_KEYCLOAK_ADMIN_ROLE:-DataportalAdmin} + KEYCLOAK_BASE_URL_ISSUER: ${DATAPORTAL_BACKEND_KEYCLOAK_BASE_URL_ISSUER:-http://auth:8080} + KEYCLOAK_BASE_URL_JWK: ${DATAPORTAL_BACKEND_KEYCLOAK_BASE_URL_JWK:-http://auth:8080} + KEYCLOAK_REALM: ${DATAPORTAL_BACKEND_KEYCLOAK_REALM:-dataportal} + #---- Mock broker + BROKER_CLIENT_MOCK_ENABLED: ${DATAPORTAL_BACKEND_BROKER_CLIENT_MOCK_ENABLED:-true} + #---- Direct broker + BROKER_CLIENT_DIRECT_ENABLED: ${DATAPORTAL_BACKEND_BROKER_CLIENT_DIRECT_ENABLED:-false} + BROKER_CLIENT_DIRECT_USE_CQL: ${DATAPORTAL_BACKEND_BROKER_CLIENT_DIRECT_USE_CQL:-false} + BROKER_CLIENT_OBFUSCATE_RESULT_COUNT: ${DATAPORTAL_BACKEND_BROKER_CLIENT_OBFUSCATE_RESULT_COUNT:-false} + BROKER_CLIENT_DIRECT_AUTH_BASIC_USERNAME: ${DATAPORTAL_BACKEND_BROKER_CLIENT_DIRECT_AUTH_BASIC_USERNAME} + BROKER_CLIENT_DIRECT_AUTH_BASIC_PASSWORD: ${DATAPORTAL_BACKEND_BROKER_CLIENT_DIRECT_AUTH_BASIC_PASSWORD} + BROKER_CLIENT_DIRECT_AUTH_OAUTH_ISSUER_URL: ${DATAPORTAL_BACKEND_BROKER_CLIENT_DIRECT_AUTH_OAUTH_ISSUER_URL:-https://keycloak.localhost:444/realms/blaze} + BROKER_CLIENT_DIRECT_AUTH_OAUTH_CLIENT_ID: ${DATAPORTAL_BACKEND_BROKER_CLIENT_DIRECT_AUTH_OAUTH_CLIENT_ID:-account} + BROKER_CLIENT_DIRECT_AUTH_OAUTH_CLIENT_SECRET: ${DATAPORTAL_BACKEND_BROKER_CLIENT_DIRECT_AUTH_OAUTH_CLIENT_SECRET:-insecure} + FLARE_WEBSERVICE_BASE_URL: ${DATAPORTAL_BACKEND_FLARE_WEBSERVICE_BASE_URL:-http://flare:8080} + CQL_SERVER_BASE_URL: ${DATAPORTAL_BACKEND_CQL_SERVER_BASE_URL:-http://fhir-server:8080/fhir} + # ---- Aktin broker + BROKER_CLIENT_AKTIN_ENABLED: ${DATAPORTAL_BACKEND_AKTIN_ENABLED:-false} + AKTIN_BROKER_BASE_URL: ${DATAPORTAL_BACKEND_AKTIN_BROKER_BASE_URL:-http://aktin-broker:8080/broker/} + AKTIN_BROKER_API_KEY: ${DATAPORTAL_BACKEND_AKTIN_BROKER_API_KEY:-xxxApiKeyAdmin123} + # ---- DSF broker + BROKER_CLIENT_DSF_ENABLED: ${DATAPORTAL_BACKEND_DSF_ENABLED:-false} + DSF_SECURITY_CACERT: ${DATAPORTAL_BACKEND_DSF_CACERT:-/opt/dataportal-security/ca.pem} + DSF_SECURITY_KEYSTORE_P12FILE: ${DATAPORTAL_BACKEND_DSF_DSF_SECURITY_KEYSTORE_P12FILE:-/opt/dataportal-security/test-user.p12} + DSF_SECURITY_KEYSTORE_PASSWORD: ${DATAPORTAL_BACKEND_DSF_SECURITY_KEYSTORE_PASSWORD:-password} + DSF_PROXY_HOST: ${DATAPORTAL_BACKEND_DSF_PROXY_HOST} + DSF_PROXY_USERNAME: ${DATAPORTAL_BACKEND_DSF_PROXY_USERNAME} + DSF_PROXY_PASSWORD: ${DATAPORTAL_BACKEND_DSF_PROXY_PASSWORD} + DSF_WEBSERVICE_BASE_URL: ${DATAPORTAL_BACKEND_DSF_WEBSERVICE_BASE_URL:-https://dsf-zars-fhir-proxy/fhir} + DSF_WEBSOCKET_URL: ${DATAPORTAL_BACKEND_DSF_WEBSOCKET_URL:-wss://dsf-zars-fhir-proxy:443/fhir/ws} + DSF_ORGANIZATION_ID: ${DATAPORTAL_BACKEND_DSF_ORGANIZATION_ID:-Test_ZARS} + # ---- privacy + PRIVACY_QUOTA_SOFT_CREATE_AMOUNT: ${DATAPORTAL_BACKEND_PRIVACY_QUOTA_SOFT_CREATE_AMOUNT:-3} + PRIVACY_QUOTA_SOFT_CREATE_INTERVALMINUTES: ${DATAPORTAL_BACKEND_PRIVACY_QUOTA_SOFT_CREATE_INTERVALMINUTES:-1} + PRIVACY_QUOTA_HARD_CREATE_AMOUNT: ${DATAPORTAL_BACKEND_PRIVACY_QUOTA_HARD_CREATE_AMOUNT:-50} + PRIVACY_QUOTA_HARD_CREATE_INTERVALMINUTES: ${DATAPORTAL_BACKEND_PRIVACY_QUOTA_HARD_CREATE_INTERVALMINUTES:-10080} + PRIVACY_QUOTA_READ_SUMMARY_POLLINGINTERVALSECONDS: ${DATAPORTAL_BACKEND_PRIVACY_QUOTA_READ_SUMMARY_POLLINGINTERVALSECONDS:-10} + PRIVACY_QUOTA_READ_DETAILED_OBFUSCATED_POLLINGINTERVALSECONDS: ${DATAPORTAL_BACKEND_PRIVACY_QUOTA_READ_DETAILED_OBFUSCATED_POLLINGINTERVALSECONDS:-10} + PRIVACY_QUOTA_READ_DETAILED_OBFUSCATED_AMOUNT: ${DATAPORTAL_BACKEND_PRIVACY_QUOTA_READ_DETAILED_OBFUSCATED_AMOUNT:-3} + PRIVACY_QUOTA_READ_DETAILED_OBFUSCATED_INTERVALSECONDS: ${DATAPORTAL_BACKEND_PRIVACY_QUOTA_READ_DETAILED_OBFUSCATED_INTERVALSECONDS:-7200} + PRIVACY_THRESHOLD_RESULTS: ${DATAPORTAL_BACKEND_PRIVACY_THRESHOLD_RESULTS:-20} + PRIVACY_THRESHOLD_SITES: ${DATAPORTAL_BACKEND_PRIVACY_THRESHOLD_SITES:-3} + PRIVACY_THRESHOLD_SITES_RESULT: ${DATAPORTAL_BACKEND_PRIVACY_THRESHOLD_SITES_RESULT} + QUERYRESULT_DISABLE_LOG_FILE_ENCRYPTION: "true" # ---- Elastic Search - ELASTIC_SEARCH_ENABLED: ${CODEX_FEASIBILITY_BACKEND_ELASTIC_SEARCH_ENABLED} - ELASTIC_SEARCH_HOST: ${CODEX_FEASIBILITY_BACKEND_ELASTIC_SEARCH_HOST} - ELASTIC_SEARCH_FILTER: ${CODEX_FEASIBILITY_BACKEND_ELASTIC_SEARCH_FILTER} + ELASTIC_SEARCH_ENABLED: ${DATAPORTAL_BACKEND_ELASTIC_SEARCH_ENABLED} + ELASTIC_SEARCH_HOST: ${DATAPORTAL_BACKEND_ELASTIC_SEARCH_HOST} + ELASTIC_SEARCH_FILTER: ${DATAPORTAL_BACKEND_ELASTIC_SEARCH_FILTER} + # ---- logging + LOG_LEVEL_SQL: ${DATAPORTAL_BACKEND_LOG_LEVEL_SQL:-warn} + LOG_LEVEL: ${DATAPORTAL_BACKEND_LOG_LEVEL:-warn} volumes: - - ${CODEX_FEASIBILITY_BACKEND_LOCAL_CONCEPT_TREE_PATH:-./ontology/codex-code-tree.json}:${CODEX_FEASIBILITY_BACKEND_ONTOLOGY_FILES_FOLDER:-/opt/codex-feasibility-backend/ontology}/codex-code-tree.json - - ${CODEX_FEASIBILITY_BACKEND_LOCAL_TERM_CODE_MAPPING_PATH:-./ontology/codex-term-code-mapping.json}:${CODEX_FEASIBILITY_BACKEND_ONTOLOGY_FILES_FOLDER:-/opt/codex-feasibility-backend/ontology}/codex-term-code-mapping.json - - ${CODEX_FEASIBILITY_BACKEND_DSF_SECURITY_DIR:-/dev/null}:/opt/codex-feasibility-backend/dsf-security/ - - ${CODEX_FEASIBILITY_BACKEND_ONTOLOGY_DB_MIGRATION_FOLDER:-../ontology/migration}:/opt/codex-feasibility-backend/ontology/migration - feasibility-db: - image: 'postgres:15-alpine' - container_name: feasibility-db + - ${DATAPORTAL_BACKEND_LOCAL_CONCEPT_TREE_PATH:-./ontology/dataportal-code-tree.json}:${DATAPORTAL_BACKEND_ONTOLOGY_FILES_FOLDER:-/opt/dataportal-backend/ontology}/dataportal-code-tree.json + - ${DATAPORTAL_BACKEND_LOCAL_TERM_CODE_MAPPING_PATH:-./ontology/dataportal-term-code-mapping.json}:${DATAPORTAL_BACKEND_ONTOLOGY_FILES_FOLDER:-/opt/dataportal-backend/ontology}/dataportal-term-code-mapping.json + - ${DATAPORTAL_BACKEND_DSF_SECURITY_DIR:-/dev/null}:/opt/dataportal-backend/dsf-security/ + - ${DATAPORTAL_BACKEND_ONTOLOGY_DB_MIGRATION_FOLDER:-../ontology/migration}:/opt/dataportal-backend/ontology/migration + + dataportal-postgres: + container_name: dataportal-postgres + image: 'postgres:16-alpine' ports: - - ${CODEX_FEASIBILITY_BACKEND_DATABASE_PORT:-5432}:5432 + - ${DATAPORTAL_BACKEND_DB_PORT:-127.0.0.1:5432}:5432 environment: - - POSTGRES_USER=codex-postgres - - POSTGRES_PASSWORD=codex-password - - POSTGRES_DB=codex_ui + POSTGRES_USER: ${DATAPORTAL_BACKEND_DATASOURCE_USERNAME:-dataportaluser} + POSTGRES_PASSWORD: ${DATAPORTAL_BACKEND_DATASOURCE_PASSWORD:-dataportalpw} + POSTGRES_DB: dataportal + restart: unless-stopped volumes: - type: volume - source: feas-backend-db-data + source: dataportal-postgres-data target: /var/lib/postgresql/data - feasibility-es: + dataportal-elastic: image: docker.elastic.co/elasticsearch/elasticsearch:8.15.0 - container_name: feasibility-es + container_name: dataportal-elastic ports: - '9200:9200' - '9300:9300' @@ -114,16 +130,16 @@ services: xpack.security.enabled: false volumes: - type: volume - source: feas-backend-es-data + source: dataportal-elastic-data target: /usr/share/elasticsearch/data init-elasticsearch: image: curlimages/curl:8.8.0 depends_on: - feasibility-es: + dataportal-elastic: condition: service_healthy environment: - ELASTIC_HOST: http://feasibility-es:9200 - ELASTIC_GIT_TAG: v3.0.0-test.1 + ELASTIC_HOST: http://dataportal-elastic:9200 + ELASTIC_GIT_TAG: v3.0.0-test.2 ELASTIC_FILEPATH: https://github.com/medizininformatik-initiative/fhir-ontology-generator/raw/TAGPLACEHOLDER/example/fdpg-ontology/ ELASTIC_FILENAME: elastic.zip # if set to false, existing indices are not overridden. @@ -135,7 +151,7 @@ services: target: /tmp/init.sh volumes: - feas-backend-db-data: - name: "feas-backend-db-data" - feas-backend-es-data: - name: "feas-backend-es-data" \ No newline at end of file + dataportal-postgres-data: + name: "dataportal-postgres-data" + dataportal-elastic-data: + name: "dataportal-elastic-data" \ No newline at end of file diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index c6e586b2..2c603dc2 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,6 +1,6 @@ #!/bin/bash -TRUSTSTORE_FILE="/opt/codex-feasibility-backend/truststore/self-signed-truststore.jks" +TRUSTSTORE_FILE="/opt/dataportal-backend/truststore/self-signed-truststore.jks" TRUSTSTORE_PASS=${TRUSTSTORE_PASS:-changeit} KEY_PASS=${KEY_PASS:-changeit} @@ -10,7 +10,7 @@ ca_files=(certs/*.pem) if [ ! "${#ca_files[@]}" -eq 0 ]; then - echo "# At least one CA file with extension *.pem found in certs folder -> starting feasibility backend with own CAs" + echo "# At least one CA file with extension *.pem found in certs folder -> starting dataportal backend with own CAs" if [[ -f "$TRUSTSTORE_FILE" ]]; then echo "## Truststore already exists -> resetting truststore" @@ -28,8 +28,8 @@ if [ ! "${#ca_files[@]}" -eq 0 ]; then done - java -Djavax.net.ssl.trustStore="$TRUSTSTORE_FILE" -Djavax.net.ssl.trustStorePassword="$TRUSTSTORE_PASS" -jar feasibility-gui-backend.jar + java $JAVA_OPTS -Djavax.net.ssl.trustStore="$TRUSTSTORE_FILE" -Djavax.net.ssl.trustStorePassword="$TRUSTSTORE_PASS" -jar dataportal-backend.jar else - echo "# No CA *.pem cert files found in /opt/codex-feasibility-backend/certs -> starting feasibility backend without own CAs" - java -jar feasibility-gui-backend.jar + echo "# No CA *.pem cert files found in /opt/dataportal-backend/certs -> starting dataportal backend without own CAs" + java $JAVA_OPTS -jar dataportal-backend.jar fi diff --git a/pom.xml b/pom.xml index c979d574..fa82226f 100644 --- a/pom.xml +++ b/pom.xml @@ -11,11 +11,11 @@ de.medizininformatik-initiative - FeasibilityGuiBackend - 6.0.0-alpha.1 + DataportalBackend + 6.0.0-alpha.2 - FeasibilityGuiBackend - Backend of the Feasibility GUI + Dataportal Backend + Backend of the Dataportal scm:git:${project.scm.url} @@ -27,7 +27,7 @@ 17 4.10.0 4.10.0 - v3.0.0-test.1 + v3.0.0-test.2 @@ -255,6 +255,12 @@ org.springframework.boot spring-boot-starter-test + + + com.vaadin.external.google + android-json + + test @@ -358,7 +364,7 @@ - feasibilityBackend + dataportalBackend @@ -476,7 +482,6 @@ mkdir ontology/migration - ontology/ui_trees ontology/dse @@ -509,7 +514,7 @@ unzip -jod - ontology/ui_trees/ + ontology/ ontology/backend.zip @@ -524,7 +529,7 @@ mv - ontology/ui_trees/R__Load_latest_ui_profile.sql + ontology/R__Load_latest_ui_profile.sql ontology/migration/ @@ -539,27 +544,12 @@ mv - ontology/ui_trees/R__load_latest_dse_profiles.sql + ontology/R__load_latest_dse_profiles.sql ontology/migration/ - - move-terminology-systems - generate-resources - - exec - - - mv - - ontology/ui_trees/terminology_systems.json - ontology/ - - - - move-profile-tree generate-resources @@ -569,7 +559,7 @@ mv - ontology/ui_trees/profile_tree.json + ontology/profile_tree.json ontology/dse diff --git a/src/main/java/de/numcodex/feasibility_gui_backend/config/WebSecurityConfig.java b/src/main/java/de/numcodex/feasibility_gui_backend/config/WebSecurityConfig.java index 0ecdf729..6c0896d9 100644 --- a/src/main/java/de/numcodex/feasibility_gui_backend/config/WebSecurityConfig.java +++ b/src/main/java/de/numcodex/feasibility_gui_backend/config/WebSecurityConfig.java @@ -37,7 +37,7 @@ public class WebSecurityConfig { public static final String KEY_SPRING_ADDONS_CONFIDENTIAL = "spring-addons-confidential"; public static final String KEY_SPRING_ADDONS_PUBLIC = "spring-addons-public"; public static final String PATH_ACTUATOR_HEALTH = "/actuator/health"; - public static final String PATH_API = "/api/v3"; + public static final String PATH_API = "/api/v4"; public static final String PATH_QUERY = "/query"; public static final String PATH_ID_MATCHER = "/{id:\\d+}"; public static final String PATH_USER_ID_MATCHER = "/by-user/{id:[\\w-]+}"; @@ -51,7 +51,7 @@ public class WebSecurityConfig { public static final String PATH_DSE = "/dse"; public static final String PATH_CODEABLE_CONCEPT = "/codeable-concept"; public static final String PATH_SWAGGER_UI = "/swagger-ui/**"; - public static final String PATH_SWAGGER_CONFIG = "/v3/api-docs/**"; + public static final String PATH_SWAGGER_CONFIG = "/v4/api-docs/**"; @Value("${app.keycloakAllowedRole}") private String keycloakAllowedRole; diff --git a/src/main/java/de/numcodex/feasibility_gui_backend/dse/v3/DseRestController.java b/src/main/java/de/numcodex/feasibility_gui_backend/dse/v4/DseRestController.java similarity index 82% rename from src/main/java/de/numcodex/feasibility_gui_backend/dse/v3/DseRestController.java rename to src/main/java/de/numcodex/feasibility_gui_backend/dse/v4/DseRestController.java index 9a459111..c8da0388 100644 --- a/src/main/java/de/numcodex/feasibility_gui_backend/dse/v3/DseRestController.java +++ b/src/main/java/de/numcodex/feasibility_gui_backend/dse/v4/DseRestController.java @@ -1,4 +1,4 @@ -package de.numcodex.feasibility_gui_backend.dse.v3; +package de.numcodex.feasibility_gui_backend.dse.v4; import de.numcodex.feasibility_gui_backend.dse.DseService; import de.numcodex.feasibility_gui_backend.dse.api.DseProfile; @@ -8,10 +8,12 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; -import java.io.FileNotFoundException; import java.util.List; -@RequestMapping("api/v3/dse") +import static de.numcodex.feasibility_gui_backend.config.WebSecurityConfig.PATH_API; +import static de.numcodex.feasibility_gui_backend.config.WebSecurityConfig.PATH_DSE; + +@RequestMapping(PATH_API + PATH_DSE) @RestController @CrossOrigin public class DseRestController { diff --git a/src/main/java/de/numcodex/feasibility_gui_backend/query/ratelimiting/RateLimitingInterceptor.java b/src/main/java/de/numcodex/feasibility_gui_backend/query/ratelimiting/RateLimitingInterceptor.java index 5d2fb2a9..39da211f 100644 --- a/src/main/java/de/numcodex/feasibility_gui_backend/query/ratelimiting/RateLimitingInterceptor.java +++ b/src/main/java/de/numcodex/feasibility_gui_backend/query/ratelimiting/RateLimitingInterceptor.java @@ -2,7 +2,7 @@ import de.numcodex.feasibility_gui_backend.config.WebSecurityConfig; import de.numcodex.feasibility_gui_backend.query.api.status.FeasibilityIssue; -import de.numcodex.feasibility_gui_backend.query.v3.QueryHandlerRestController; +import de.numcodex.feasibility_gui_backend.query.v4.QueryHandlerRestController; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/main/java/de/numcodex/feasibility_gui_backend/query/v3/QueryHandlerRestController.java b/src/main/java/de/numcodex/feasibility_gui_backend/query/v4/QueryHandlerRestController.java similarity index 96% rename from src/main/java/de/numcodex/feasibility_gui_backend/query/v3/QueryHandlerRestController.java rename to src/main/java/de/numcodex/feasibility_gui_backend/query/v4/QueryHandlerRestController.java index 461cf177..b84a1e37 100644 --- a/src/main/java/de/numcodex/feasibility_gui_backend/query/v3/QueryHandlerRestController.java +++ b/src/main/java/de/numcodex/feasibility_gui_backend/query/v4/QueryHandlerRestController.java @@ -1,4 +1,4 @@ -package de.numcodex.feasibility_gui_backend.query.v3; +package de.numcodex.feasibility_gui_backend.query.v4; import com.fasterxml.jackson.core.JsonProcessingException; import de.numcodex.feasibility_gui_backend.config.WebSecurityConfig; @@ -37,11 +37,14 @@ import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Mono; +import static de.numcodex.feasibility_gui_backend.config.WebSecurityConfig.PATH_API; +import static de.numcodex.feasibility_gui_backend.config.WebSecurityConfig.PATH_QUERY; + /* Rest Interface for the UI to send queries from the ui to the ui backend. */ -@RequestMapping("api/v3/query") -@RestController("QueryHandlerRestController-v3") +@RequestMapping(PATH_API + PATH_QUERY) +@RestController("QueryHandlerRestController-v4") @Slf4j @CrossOrigin(origins = "${cors.allowedOrigins}", exposedHeaders = {HttpHeaders.LOCATION, HttpHeaders.RETRY_AFTER}) public class QueryHandlerRestController { @@ -171,7 +174,7 @@ private URI buildResultLocationUri(HttpServletRequest httpServletRequest, : ServletUriComponentsBuilder.fromRequestUri(httpServletRequest); return uriBuilder.replacePath("") - .pathSegment("api", "v3", "query", String.valueOf(queryId)) + .pathSegment("api", "v4", "query", String.valueOf(queryId)) .build() .toUri(); } @@ -179,7 +182,7 @@ private URI buildResultLocationUri(HttpServletRequest httpServletRequest, @GetMapping("") public List getQueryList( @RequestParam(name = "filter", required = false) String filter, - @RequestParam(value = "skipValidation", required = false, defaultValue = "false") boolean skipValidation, + @RequestParam(value = "skip-validation", required = false, defaultValue = "false") boolean skipValidation, Principal principal) { var userId = principal.getName(); var savedOnly = (filter != null && filter.equalsIgnoreCase("saved")); @@ -295,7 +298,7 @@ public List getQueryListForUser( @GetMapping("/{id}") public ResponseEntity getQuery(@PathVariable("id") Long queryId, - @RequestParam(value = "skipValidation", required = false, defaultValue = "false") boolean skipValidation, + @RequestParam(value = "skip-validation", required = false, defaultValue = "false") boolean skipValidation, Authentication authentication) throws JsonProcessingException { if (!hasAccess(queryId, authentication)) { return new ResponseEntity<>(HttpStatus.FORBIDDEN); diff --git a/src/main/java/de/numcodex/feasibility_gui_backend/query/v3/QueryTemplateHandlerRestController.java b/src/main/java/de/numcodex/feasibility_gui_backend/query/v4/QueryTemplateHandlerRestController.java similarity index 92% rename from src/main/java/de/numcodex/feasibility_gui_backend/query/v3/QueryTemplateHandlerRestController.java rename to src/main/java/de/numcodex/feasibility_gui_backend/query/v4/QueryTemplateHandlerRestController.java index e42b9cce..b95f925b 100644 --- a/src/main/java/de/numcodex/feasibility_gui_backend/query/v3/QueryTemplateHandlerRestController.java +++ b/src/main/java/de/numcodex/feasibility_gui_backend/query/v4/QueryTemplateHandlerRestController.java @@ -1,4 +1,4 @@ -package de.numcodex.feasibility_gui_backend.query.v3; +package de.numcodex.feasibility_gui_backend.query.v4; import com.fasterxml.jackson.core.JsonProcessingException; import de.numcodex.feasibility_gui_backend.query.QueryHandlerService; @@ -22,11 +22,13 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder; +import static de.numcodex.feasibility_gui_backend.config.WebSecurityConfig.*; + /* Rest Interface for the UI to send and receive query templates from the backend. */ -@RequestMapping("api/v3/query/template") -@RestController("QueryTemplateHandlerRestController-v3") +@RequestMapping(PATH_API + PATH_QUERY + PATH_TEMPLATE) +@RestController("QueryTemplateHandlerRestController-v4") @Slf4j @CrossOrigin(origins = "${cors.allowedOrigins}", exposedHeaders = "Location") public class QueryTemplateHandlerRestController { @@ -61,7 +63,7 @@ public ResponseEntity storeQueryTemplate(@Valid @RequestBody QueryTempla : ServletUriComponentsBuilder.fromRequestUri(httpServletRequest); var uriString = uriBuilder.replacePath("") - .pathSegment("api", "v3", "query", "template", String.valueOf(queryId)) + .pathSegment("api", "v4", "query", "template", String.valueOf(queryId)) .build() .toUriString(); HttpHeaders httpHeaders = new HttpHeaders(); @@ -71,7 +73,7 @@ public ResponseEntity storeQueryTemplate(@Valid @RequestBody QueryTempla @GetMapping(path = "/{queryId}") public ResponseEntity getQueryTemplate(@PathVariable(value = "queryId") Long queryId, - @RequestParam(value = "skipValidation", required = false, defaultValue = "false") boolean skipValidation, + @RequestParam(value = "skip-validation", required = false, defaultValue = "false") boolean skipValidation, Principal principal) { try { @@ -96,7 +98,7 @@ public ResponseEntity getQueryTemplate(@PathVariable(value = "queryId") @GetMapping(path = "") public ResponseEntity getQueryTemplates( - @RequestParam(value = "skipValidation", required = false, defaultValue = "false") boolean skipValidation, + @RequestParam(value = "skip-validation", required = false, defaultValue = "false") boolean skipValidation, Principal principal) { var queries = queryHandlerService.getQueryTemplatesForAuthor(principal.getName()); diff --git a/src/main/java/de/numcodex/feasibility_gui_backend/terminology/TerminologyService.java b/src/main/java/de/numcodex/feasibility_gui_backend/terminology/TerminologyService.java index 19794181..d51edfb6 100644 --- a/src/main/java/de/numcodex/feasibility_gui_backend/terminology/TerminologyService.java +++ b/src/main/java/de/numcodex/feasibility_gui_backend/terminology/TerminologyService.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import de.numcodex.feasibility_gui_backend.terminology.api.*; import de.numcodex.feasibility_gui_backend.terminology.persistence.*; @@ -13,12 +12,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import java.io.File; import java.io.IOException; import java.net.URL; import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; @Service @Slf4j @@ -28,11 +24,6 @@ public class TerminologyService { private final TermCodeRepository termCodeRepository; - private final ContextualizedTermCodeRepository contextualizedTermCodeRepository; - - private final MappingRepository mappingRepository; - - private final String uiProfilePath; @Getter private final List terminologySystems; @@ -40,141 +31,17 @@ public class TerminologyService { @NonNull private ObjectMapper jsonUtil; - @Value("${app.ontologyOrder}") - private List sortedCategories; - private Map terminologyEntries = new HashMap<>(); - private List categoryEntries = new ArrayList<>(); - private Map terminologyEntriesWithOnlyDirectChildren = new HashMap<>(); - private Map> selectableEntriesByCategory = new HashMap<>(); - public TerminologyService(@Value("${app.ontologyFolder}") String uiProfilePath, - @Value("${app.terminologySystemsFile}") String terminologySystemsFilename, + public TerminologyService(@Value("${app.terminologySystemsFile}") String terminologySystemsFilename, UiProfileRepository uiProfileRepository, TermCodeRepository termCodeRepository, - ContextualizedTermCodeRepository contextualizedTermCodeRepository, - MappingRepository mappingRepository, ObjectMapper jsonUtil) throws IOException { - this.uiProfilePath = uiProfilePath; - readInTerminologyEntries(); - generateTerminologyEntriesWithoutDirectChildren(); - generateSelectableEntriesByCategory(); this.uiProfileRepository = uiProfileRepository; this.termCodeRepository = termCodeRepository; - this.contextualizedTermCodeRepository = contextualizedTermCodeRepository; - this.mappingRepository = mappingRepository; this.jsonUtil = jsonUtil; this.terminologySystems = jsonUtil.readValue(new URL("file:" + terminologySystemsFilename), new TypeReference<>() {}); } - private void readInTerminologyEntries() throws IOException { - var files = getFilePathsUiProfiles(); - /*for (var filename : files) { - if (!filename.toLowerCase().endsWith(".json")) { - log.trace("Skipping non-json file: {}", filename); - continue; - } - var objectMapper = new ObjectMapper(); - objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - try { - var terminology_entry = (objectMapper.readValue( - new URL("file:" + uiProfilePath + "/" + filename), - TerminologyEntry.class)); - terminologyEntries.put(terminology_entry.getId(), terminology_entry); - categoryEntries.add(new CategoryEntry(terminology_entry.getId(), - terminology_entry.getDisplay())); - } catch (IOException e) { - throw new IOException("Could not read terminology files", e); - } - }*/ - } - - private void generateTerminologyEntriesWithoutDirectChildren() { - for (var entry : terminologyEntries.values()) { - generateTerminologyEntriesWithOnlyDirectChildren(entry); - } - } - - private Set getFilePathsUiProfiles() { - log.info("Ui Profile Path -> {}", uiProfilePath); - - return Stream.of( - Objects.requireNonNull(new File(uiProfilePath).listFiles())) - .filter(file -> !file.isDirectory()) - .map(File::getName) - .collect(Collectors.toSet()); - } - - private void generateTerminologyEntriesWithOnlyDirectChildren(TerminologyEntry terminologyTree) { - - var entryWithOnlyDirectChildren = TerminologyEntry.copyWithDirectChildren(terminologyTree); - terminologyEntriesWithOnlyDirectChildren - .put(entryWithOnlyDirectChildren.getId(), entryWithOnlyDirectChildren); - for (var child : terminologyTree.getChildren()) { - generateTerminologyEntriesWithOnlyDirectChildren(child); - } - } - - private Set getSelectableEntries(TerminologyEntry terminologyEntry) { - Set selectableEntries = new HashSet<>(); - if (terminologyEntry.isSelectable()) { - var terminologyEntryWithoutChildren = TerminologyEntry.copyWithoutChildren(terminologyEntry); - selectableEntries.add(terminologyEntryWithoutChildren); - } - for (var child : terminologyEntry.getChildren()) { - selectableEntries.addAll(getSelectableEntries(child)); - } - return selectableEntries; - } - - private void generateSelectableEntriesByCategory() { - for (var terminologyEntry : terminologyEntries.values()) { - selectableEntriesByCategory - .put(terminologyEntry.getId(), getSelectableEntries(terminologyEntry)); - } - } - - public TerminologyEntry getEntry(UUID nodeId) throws NodeNotFoundException { - TerminologyEntry terminologyEntry = terminologyEntriesWithOnlyDirectChildren.get(nodeId); - if (terminologyEntry == null) { - throw new NodeNotFoundException(); - } - return terminologyEntry; - } - - public List getCategories() { - var sortedCategoryEntries = new ArrayList(); - - List found = categoryEntries.stream().filter(value -> sortedCategories.contains(value.getDisplay())).collect(Collectors.toList()); - List notFound = categoryEntries.stream().filter(value -> !sortedCategories.contains(value.getDisplay())).collect(Collectors.toList()); - - found.sort(Comparator.comparing(value -> sortedCategories.indexOf(value.getDisplay()))); - notFound.sort(Comparator.comparing(CategoryEntry::getDisplay)); - - sortedCategoryEntries.addAll(found); - sortedCategoryEntries.addAll(notFound); - - return sortedCategoryEntries; - } - - public List getSelectableEntries(String query, UUID categoryId) { - if (categoryId != null) { - return selectableEntriesByCategory.get(categoryId).stream() - .filter((terminologyEntry -> matchesQuery(query, terminologyEntry))).sorted((Comparator - .comparingInt(val -> val.getDisplay().length()))).limit(20) - .collect(Collectors.toList()); - } else { - Set allSelectableEntries = new HashSet<>(); - for (var selectableEntries : selectableEntriesByCategory.values()) { - allSelectableEntries.addAll(selectableEntries); - } - log.debug("Selectable entries: {}", allSelectableEntries.size()); - return allSelectableEntries.stream() - .filter((terminologyEntry -> matchesQuery(query, terminologyEntry))).sorted((Comparator - .comparingInt(val -> val.getDisplay().length()))).limit(20) - .collect(Collectors.toList()); - } - } - public String getUiProfile(String contextualizedTermCodeHash) throws UiProfileNotFoundException { Optional uiProfile = uiProfileRepository.findByContextualizedTermcodeHash(contextualizedTermCodeHash); @@ -185,16 +52,6 @@ public String getUiProfile(String contextualizedTermCodeHash) } } - public String getMapping(String contextualizedTermCodeHash) - throws MappingNotFoundException { - Optional mapping = mappingRepository.findByContextualizedTermcodeHash(contextualizedTermCodeHash); - if (mapping.isPresent()) { - return mapping.get().getContent(); - } else { - throw new MappingNotFoundException(); - } - } - public boolean isExistingTermCode(String system, String code, String version) { return (version == null) ? termCodeRepository.existsTermCode(system, code) : termCodeRepository.existsTermCode(system, code, version); } @@ -204,18 +61,6 @@ public static int min(int... numbers) { .min().orElse(Integer.MAX_VALUE); } - private boolean matchesQuery(String query, TerminologyEntry terminologyEntry) { - return terminologyEntry.getDisplay().toLowerCase().startsWith(query.toLowerCase()) || - Arrays.stream(terminologyEntry.getDisplay().toLowerCase().split(" ")) - .anyMatch(var -> var.startsWith(query.toLowerCase())) || - (terminologyEntry.getTermCodes().stream().anyMatch(termCode -> termCode.code().toLowerCase() - .startsWith(query.toLowerCase()))); - } - - public List getIntersection(String criteriaSetUrl, List contextTermCodeHashList) { - return contextualizedTermCodeRepository.filterByCriteriaSetUrl(criteriaSetUrl, contextTermCodeHashList); - } - public List getCriteriaProfileData(List criteriaIds) { List results = new ArrayList<>(); diff --git a/src/main/java/de/numcodex/feasibility_gui_backend/terminology/v3/TerminologyEsRestController.java b/src/main/java/de/numcodex/feasibility_gui_backend/terminology/v3/TerminologyEsRestController.java deleted file mode 100644 index 608c7799..00000000 --- a/src/main/java/de/numcodex/feasibility_gui_backend/terminology/v3/TerminologyEsRestController.java +++ /dev/null @@ -1,55 +0,0 @@ -package de.numcodex.feasibility_gui_backend.terminology.v3; - -import de.numcodex.feasibility_gui_backend.terminology.api.EsSearchResult; -import de.numcodex.feasibility_gui_backend.terminology.api.EsSearchResultEntry; -import de.numcodex.feasibility_gui_backend.terminology.es.TerminologyEsService; -import de.numcodex.feasibility_gui_backend.terminology.es.model.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequestMapping("") -@ConditionalOnExpression("${app.elastic.enabled}") -@CrossOrigin -public class TerminologyEsRestController { - - private TerminologyEsService terminologyEsService; - - @Autowired - public TerminologyEsRestController(TerminologyEsService terminologyEsService) { - this.terminologyEsService = terminologyEsService; - } - - @GetMapping("api/v3/terminology/search/filter") - public List getAvailableFilters() { - return terminologyEsService.getAvailableFilters(); - } - - @GetMapping("api/v3/terminology/entry/search") - public EsSearchResult searchOntologyItemsCriteriaQuery2(@RequestParam("searchterm") String keyword, - @RequestParam(value = "criteria-sets", required = false) List criteriaSets, - @RequestParam(value = "contexts", required = false) List contexts, - @RequestParam(value = "kds-modules", required = false) List kdsModules, - @RequestParam(value = "terminologies", required = false) List terminologies, - @RequestParam(value = "availability", required = false, defaultValue = "false") boolean availability, - @RequestParam(value = "page-size", required = false, defaultValue = "20") int pageSize, - @RequestParam(value = "page", required = false, defaultValue = "0") int page) { - - - return terminologyEsService - .performOntologySearchWithPaging(keyword, criteriaSets, contexts, kdsModules, terminologies, availability, pageSize, page); - } - - @GetMapping("api/v3/terminology/entry/{hash}/relations") - public OntologyItemRelationsDocument getOntologyItemRelationsByHash(@PathVariable("hash") String hash) { - return terminologyEsService.getOntologyItemRelationsByHash(hash); - } - - @GetMapping("api/v3/terminology/entry/{hash}") - public EsSearchResultEntry getOntologyItemByHash(@PathVariable("hash") String hash) { - return terminologyEsService.getSearchResultEntryByHash(hash); - } -} diff --git a/src/main/java/de/numcodex/feasibility_gui_backend/terminology/v3/TerminologyRestController.java b/src/main/java/de/numcodex/feasibility_gui_backend/terminology/v3/TerminologyRestController.java deleted file mode 100644 index 08e65a12..00000000 --- a/src/main/java/de/numcodex/feasibility_gui_backend/terminology/v3/TerminologyRestController.java +++ /dev/null @@ -1,75 +0,0 @@ -package de.numcodex.feasibility_gui_backend.terminology.v3; - - -import de.numcodex.feasibility_gui_backend.terminology.TerminologyService; -import de.numcodex.feasibility_gui_backend.terminology.api.*; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.*; - -import java.util.List; -import java.util.UUID; - -/* - Rest interface to get the terminology definitions from the UI backend which itself request the - terminology information from the ui terminology service - */ - - -@RequestMapping("api/v3/terminology") -@RestController -@CrossOrigin -public class TerminologyRestController { - - private final TerminologyService terminologyService; - - @Autowired - public TerminologyRestController(TerminologyService terminologyService) { - this.terminologyService = terminologyService; - } - - @GetMapping("criteria-profile-data") - public List getCriteriaProfileData(@RequestParam List ids) { - return terminologyService.getCriteriaProfileData(ids); - } - - @GetMapping("entries/{nodeId}") - public TerminologyEntry getEntry(@PathVariable UUID nodeId) { - return terminologyService.getEntry(nodeId); - } - - @GetMapping("categories") - public List getCategories() { - return terminologyService.getCategories(); - } - - @GetMapping("entries") - public List searchSelectableEntries(@RequestParam("query") String query, - @RequestParam(value = "categoryId", required = false) UUID categoryId) { - return terminologyService.getSelectableEntries(query, categoryId); - } - - @GetMapping(value = "{contextualizedTermcodeId}/ui_profile", produces = MediaType.APPLICATION_JSON_VALUE) - public String getUiProfile( - @PathVariable("contextualizedTermcodeId") String contextualizedTermcodeId) { - return terminologyService.getUiProfile(contextualizedTermcodeId); - } - - @GetMapping(value = "{contextualizedTermcodeId}/mapping", produces = MediaType.APPLICATION_JSON_VALUE) - public String getMapping( - @PathVariable("contextualizedTermcodeId") String contextualizedTermcodeId) { - return terminologyService.getMapping(contextualizedTermcodeId); - } - - @PostMapping(value = "criteria-set/intersect", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public List getIntersection( - @RequestParam("criteriaSetUrl") String criteriaSetUrl, - @RequestBody List contextTermCodeHashList) { - return terminologyService.getIntersection(criteriaSetUrl, contextTermCodeHashList); - } - - @GetMapping(value = "systems", produces = MediaType.APPLICATION_JSON_VALUE) - public List getTerminologySystems() { - return terminologyService.getTerminologySystems(); - } -} diff --git a/src/main/java/de/numcodex/feasibility_gui_backend/terminology/v3/CodeableConceptEsRestController.java b/src/main/java/de/numcodex/feasibility_gui_backend/terminology/v4/CodeableConceptRestController.java similarity index 87% rename from src/main/java/de/numcodex/feasibility_gui_backend/terminology/v3/CodeableConceptEsRestController.java rename to src/main/java/de/numcodex/feasibility_gui_backend/terminology/v4/CodeableConceptRestController.java index 1e076bce..792e542b 100644 --- a/src/main/java/de/numcodex/feasibility_gui_backend/terminology/v3/CodeableConceptEsRestController.java +++ b/src/main/java/de/numcodex/feasibility_gui_backend/terminology/v4/CodeableConceptRestController.java @@ -1,4 +1,4 @@ -package de.numcodex.feasibility_gui_backend.terminology.v3; +package de.numcodex.feasibility_gui_backend.terminology.v4; import de.numcodex.feasibility_gui_backend.common.api.TermCode; import de.numcodex.feasibility_gui_backend.terminology.api.CcSearchResult; @@ -11,15 +11,15 @@ import java.util.List; @RestController -@RequestMapping("api/v3/codeable-concept") +@RequestMapping("api/v4/codeable-concept") @ConditionalOnExpression("${app.elastic.enabled}") @CrossOrigin -public class CodeableConceptEsRestController { +public class CodeableConceptRestController { private CodeableConceptService codeableConceptService; @Autowired - public CodeableConceptEsRestController(CodeableConceptService codeableConceptService) { + public CodeableConceptRestController(CodeableConceptService codeableConceptService) { this.codeableConceptService = codeableConceptService; } diff --git a/src/main/java/de/numcodex/feasibility_gui_backend/terminology/v4/TerminologyRestController.java b/src/main/java/de/numcodex/feasibility_gui_backend/terminology/v4/TerminologyRestController.java new file mode 100644 index 00000000..ba6ef2e5 --- /dev/null +++ b/src/main/java/de/numcodex/feasibility_gui_backend/terminology/v4/TerminologyRestController.java @@ -0,0 +1,77 @@ +package de.numcodex.feasibility_gui_backend.terminology.v4; + + +import de.numcodex.feasibility_gui_backend.terminology.TerminologyService; +import de.numcodex.feasibility_gui_backend.terminology.api.*; +import de.numcodex.feasibility_gui_backend.terminology.es.TerminologyEsService; +import de.numcodex.feasibility_gui_backend.terminology.es.model.OntologyItemRelationsDocument; +import de.numcodex.feasibility_gui_backend.terminology.es.model.TermFilter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/* + Rest interface to get the terminology definitions from the UI backend which itself request the + terminology information from the ui terminology service + */ + + +@RequestMapping("api/v4/terminology") +@RestController +@CrossOrigin +@ConditionalOnExpression("${app.elastic.enabled}") +public class TerminologyRestController { + + private final TerminologyService terminologyService; + + private TerminologyEsService terminologyEsService; + + @Autowired + public TerminologyRestController(TerminologyService terminologyService, TerminologyEsService terminologyEsService) { + this.terminologyService = terminologyService; + this.terminologyEsService = terminologyEsService; + } + + @GetMapping("criteria-profile-data") + public List getCriteriaProfileData(@RequestParam List ids) { + return terminologyService.getCriteriaProfileData(ids); + } + + @GetMapping(value = "systems", produces = MediaType.APPLICATION_JSON_VALUE) + public List getTerminologySystems() { + return terminologyService.getTerminologySystems(); + } + + @GetMapping("search/filter") + public List getAvailableFilters() { + return terminologyEsService.getAvailableFilters(); + } + + @GetMapping("entry/search") + public EsSearchResult searchOntologyItemsCriteriaQuery2(@RequestParam("searchterm") String keyword, + @RequestParam(value = "criteria-sets", required = false) List criteriaSets, + @RequestParam(value = "contexts", required = false) List contexts, + @RequestParam(value = "kds-modules", required = false) List kdsModules, + @RequestParam(value = "terminologies", required = false) List terminologies, + @RequestParam(value = "availability", required = false, defaultValue = "false") boolean availability, + @RequestParam(value = "page-size", required = false, defaultValue = "20") int pageSize, + @RequestParam(value = "page", required = false, defaultValue = "0") int page) { + + + return terminologyEsService + .performOntologySearchWithPaging(keyword, criteriaSets, contexts, kdsModules, terminologies, availability, pageSize, page); + } + + @GetMapping("entry/{hash}/relations") + public OntologyItemRelationsDocument getOntologyItemRelationsByHash(@PathVariable("hash") String hash) { + return terminologyEsService.getOntologyItemRelationsByHash(hash); + } + + @GetMapping("entry/{hash}") + public EsSearchResultEntry getOntologyItemByHash(@PathVariable("hash") String hash) { + return terminologyEsService.getSearchResultEntryByHash(hash); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0226d330..560a4550 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,11 +2,11 @@ server: port: 8090 spring: application: - name: codex-feasibility-gui-backend + name: dataportal-backend datasource: - url: jdbc:postgresql://${FEASIBILITY_DATABASE_HOST:localhost}:${FEASIBILITY_DATABASE_PORT:5432}/${FEASIBILITY_DATABASE_DBNAME:codex_ui} - username: ${FEASIBILITY_DATABASE_USER:codex-postgres} - password: ${FEASIBILITY_DATABASE_PASSWORD:codex-password} + url: jdbc:postgresql://${DATABASE_HOST:localhost}:${DATABASE_PORT:5432}/${DATABASE_DBNAME:dataportal} + username: ${DATABASE_USER:dataportaluser} + password: ${DATABASE_PASSWORD:dataportalpw} driverClassName: org.postgresql.Driver jpa: database-platform: org.hibernate.dialect.PostgreSQLDialect @@ -23,12 +23,12 @@ spring: oauth2: resourceserver: jwt: - issuer-uri: ${KEYCLOAK_BASE_URL_ISSUER:http://localhost:8080}/realms/${KEYCLOAK_REALM:feasibility} - jwk-set-uri: ${KEYCLOAK_BASE_URL_JWK:http://localhost:8080}/realms/${KEYCLOAK_REALM:feasibility}/protocol/openid-connect/certs + issuer-uri: ${KEYCLOAK_BASE_URL_ISSUER:http://localhost:8080}/realms/${KEYCLOAK_REALM:dataportal} + jwk-set-uri: ${KEYCLOAK_BASE_URL_JWK:http://localhost:8080}/realms/${KEYCLOAK_REALM:dataportal}/protocol/openid-connect/certs springdoc: swagger-ui: - url: /v3/api-docs/swagger.yaml + url: /v4/api-docs/swagger.yaml tryItOutEnabled: false management: endpoints: @@ -43,17 +43,15 @@ management: app: dseProfileTreeFile: ${DSE_PROFILE_TREE_FILE:ontology/dse/profile_tree.json} terminologySystemsFile: ${TERMINOLOGY_SYSTEMS_FILE:ontology/terminology_systems.json} - ontologyFolder: ${ONTOLOGY_FILES_FOLDER_UI:ontology/ui_trees} - ontologyOrder: ${ONTOLOGY_ORDER:Diagnose, Prozedur, Person, Laboruntersuchung, Medikamentenverabreichung, Bioprobe, Einwilligung} mappingsFile: ${MAPPINGS_FILE:ontology/mapping_cql.json} conceptTreeFile: ${CONCEPT_TREE_FILE:ontology/mapping_tree.json} fhirTranslationEnabled: ${FHIR_TRANSLATE_ENABLED:false} cqlTranslationEnabled: ${CQL_TRANSLATE_ENABLED:true} apiBaseUrl: ${API_BASE_URL:} enableQueryValidation: ${QUERY_VALIDATION_ENABLED:true} - keycloakAllowedRole: ${KEYCLOAK_ALLOWED_ROLE:FeasibilityUser} - keycloakPowerRole: ${KEYCLOAK_POWER_ROLE:FeasibilityPowerUser} - keycloakAdminRole: ${KEYCLOAK_ADMIN_ROLE:FeasibilityAdmin} + keycloakAllowedRole: ${KEYCLOAK_ALLOWED_ROLE:DataportalUser} + keycloakPowerRole: ${KEYCLOAK_POWER_ROLE:DataportalPowerUser} + keycloakAdminRole: ${KEYCLOAK_ADMIN_ROLE:DataportalAdmin} queryResultExpiryMinutes: ${QUERYRESULT_EXPIRY_MINUTES:1} maxSavedQueriesPerUser: ${MAX_SAVED_QUERIES_PER_USER:10} broker: diff --git a/src/main/resources/static/v3/api-docs/swagger.yaml b/src/main/resources/static/v4/api-docs/swagger.yaml similarity index 73% rename from src/main/resources/static/v3/api-docs/swagger.yaml rename to src/main/resources/static/v4/api-docs/swagger.yaml index f187a8df..100322c5 100644 --- a/src/main/resources/static/v3/api-docs/swagger.yaml +++ b/src/main/resources/static/v4/api-docs/swagger.yaml @@ -1,13 +1,10 @@ -openapi: 3.0.3 +openapi: 3.1.0 info: - title: MII Feasibility Backend REST API - description: todo - contact: - email: noreply@todo.de + title: Dataportal Backend REST API license: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html - version: 5.0.0 + version: 6.0.0 externalDocs: description: Check out the github repository url: https://github.com/medizininformatik-initiative/feasibility-backend @@ -15,19 +12,16 @@ servers: - url: https://to.be.defined variables: basePath: - default: /api/v3 + default: /api/v4 tags: - name: query description: operations for queries - externalDocs: - description: More information - url: http://link.to.confluence - name: templates description: operations to work with query templates - name: terminology description: operations to work with the ontology - name: codeable concepts - description: NOT IMPLEMENTED YET + description: operations on codeable concepts - name: dse description: Data Selection and Extraction - name: intrinsics @@ -45,7 +39,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/StructuredQuery' + $ref: "#/components/schemas/StructuredQuery" required: true responses: 201: @@ -55,25 +49,21 @@ paths: description: Path to the result of your newly created query schema: type: string - example: "https://to.be.defined/api/v3/query/42" + examples: + - https://to.be.defined/api/v4/query/42 content: {} 401: description: Unauthorized - please login first - content: {} 403: description: Forbidden - insufficient access rights - content: {} 422: description: Invalid input - content: {} 429: description: Too many requests in a given amount of time (configurable) - content: {} 500: description: Dispatch error - content: {} security: - - feasibility_auth: + - dataportal_auth: - user x-codegen-request-body-name: body get: @@ -91,7 +81,7 @@ paths: type: string enum: - saved - - name: skipValidation + - name: skip-validation in: query description: If true, do not validate the query and do not include a list of invalid terms required: false @@ -106,12 +96,11 @@ paths: schema: type: array items: - $ref: '#/components/schemas/QueryListEntry' + $ref: "#/components/schemas/QueryListEntry" 401: description: Unauthorized - please login first - content: {} security: - - feasibility_auth: + - dataportal_auth: - user /query/validate: post: @@ -125,7 +114,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/StructuredQuery' + $ref: "#/components/schemas/StructuredQuery" required: true responses: 200: @@ -135,13 +124,11 @@ paths: schema: type: array items: - $ref: '#/components/schemas/StructuredQuery' + $ref: "#/components/schemas/StructuredQuery" 400: description: Query does not adhere to json schema - content: { } 401: description: Unauthorized - please login first - content: { } /query/by-user/{userId}: get: tags: @@ -163,13 +150,6 @@ paths: type: string enum: - saved - - name: skipValidation - in: query - description: If true, do not validate the query and do not include a list of invalid terms - required: false - schema: - type: boolean - default: false responses: 200: description: successful operation @@ -178,18 +158,15 @@ paths: schema: type: array items: - $ref: '#/components/schemas/QueryListEntry' + $ref: "#/components/schemas/QueryListEntry" 401: description: Unauthorized - please login first - content: {} 403: description: Forbidden - insufficient access rights - content: {} 404: description: User not found - content: {} security: - - feasibility_auth: + - dataportal_auth: - admin /query/{queryId}: get: @@ -206,7 +183,7 @@ paths: schema: type: integer format: int64 - - name: skipValidation + - name: skip-validation in: query description: If true, do not validate the query and do not include a list of invalid terms required: false @@ -219,18 +196,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Query' + $ref: "#/components/schemas/Query" 401: description: Unauthorized - please login first - content: { } 403: description: Forbidden - insufficient access rights - content: {} 404: description: Query not found - content: {} security: - - feasibility_auth: + - dataportal_auth: - user - admin /query/{queryId}/content: @@ -254,18 +228,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/StructuredQuery' + $ref: "#/components/schemas/StructuredQuery" 401: description: Unauthorized - please login first - content: { } 403: description: Forbidden - insufficient access rights - content: {} 404: description: Query not found - content: {} security: - - feasibility_auth: + - dataportal_auth: - admin - user /query/{queryId}/summary-result: @@ -289,21 +260,17 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/QueryResultSummary' + $ref: "#/components/schemas/QueryResultSummary" 401: description: Unauthorized - please login first - content: { } 403: description: Forbidden - insufficient access rights - content: {} 404: description: Query not found - content: {} 429: description: Too many requests - content: {} security: - - feasibility_auth: + - dataportal_auth: - admin - user /query/{queryId}/detailed-result: @@ -327,18 +294,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/QueryResult' + $ref: "#/components/schemas/QueryResult" 401: description: Unauthorized - please login first - content: { } 403: description: Forbidden - insufficient access rights - content: {} 404: description: Query not found - content: {} security: - - feasibility_auth: + - dataportal_auth: - admin /query/{queryId}/detailed-obfuscated-result: get: @@ -361,32 +325,28 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/QueryResultObfuscated' + $ref: "#/components/schemas/QueryResultObfuscated" 401: description: Unauthorized - please login first - content: { } 403: description: Forbidden - insufficient access rights - content: {} 404: description: Query not found - content: {} 429: description: Too many requests - content: {} security: - - feasibility_auth: + - dataportal_auth: - admin - user /query/detailed-obfuscated-result-rate-limit: get: - summary: "GET api/v3/query/detailed-obfuscated-result-rate-limit" - operationId: "getDetailedObfuscatedResultRateLimit" + summary: get the rate limit for detailed obfuscated results + operationId: getDetailedObfuscatedResultRateLimit responses: - "200": - description: "OK" + 200: + description: OK content: - 'application/json': + application/json: schema: $ref: "#/components/schemas/QueryResultRateLimit" /query/{queryId}/saved: @@ -408,18 +368,17 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/SavedQuery' + $ref: "#/components/schemas/SavedQuery" required: true responses: 200: description: Saved Query successfully stored content: - '*/*': + application/json: schema: $ref: "#/components/schemas/SavedQuerySlots" 401: description: Unauthorized - please login first - content: {} 403: description: Forbidden - insufficient access rights, or no free slots left content: @@ -442,18 +401,16 @@ paths: type: string 404: description: The query for which the additional information should be stored could not be found - content: {} 409: description: Query has already been saved - content: {} security: - - feasibility_auth: + - dataportal_auth: - user put: tags: - query - summary: "Update a saved query. Only label and comment can be changed" - operationId: "updateSavedQuery" + summary: Update a saved query. Only label and comment can be changed. + operationId: updateSavedQuery parameters: - name: queryId in: path @@ -467,67 +424,61 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/SavedQuery' + $ref: "#/components/schemas/SavedQuery" required: true responses: 200: description: Saved Query successfully updated content: - '*/*': + application/json: schema: $ref: "#/components/schemas/SavedQuerySlots" 401: description: Unauthorized - please login first - content: {} 403: description: Forbidden - insufficient access rights - content: {} 404: description: The query to be updated could not be found - content: {} delete: tags: - query - summary: "Remove a saved query from a given query" - operationId: "deleteSavedQuery" + summary: Remove a saved query from a given query + operationId: deleteSavedQuery parameters: - - name: "queryId" - in: "path" + - name: queryId + in: path required: true schema: type: integer format: int64 responses: 200: - description: "OK" + description: OK content: - '*/*': + application/json: schema: $ref: "#/components/schemas/SavedQuerySlots" 401: description: Unauthorized - please login first - content: { } 403: description: Forbidden - insufficient access rights - content: {} 404: description: The query for which the additional information should be stored could not be found - content: {} /query/saved-query-slots: get: tags: - query - summary: "Show how many saved query slots a user already used and how many he has left." - operationId: "getSavedQuerySlots" + summary: Show how many saved query slots a user already used and how many he has left. + operationId: getSavedQuerySlots responses: - "200": - description: "OK" + 200: + description: OK content: - '*/*': + application/json: schema: $ref: "#/components/schemas/SavedQuerySlots" security: - - feasibility_auth: + - dataportal_auth: - user /query/template: post: @@ -540,7 +491,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/QueryTemplate' + $ref: "#/components/schemas/QueryTemplate" required: true responses: 201: @@ -550,19 +501,17 @@ paths: description: Path to the newly stored query template schema: type: string - example: "https://to.be.defined/api/v3/query/template/42" - content: {} + format: uri + examples: + - https://to.be.defined/api/v4/query/template/42 401: description: Unauthorized - please login first - content: { } 403: description: Forbidden - insufficient access rights - content: {} 409: description: Query with the same label exists for this user - content: {} security: - - feasibility_auth: + - dataportal_auth: - user get: tags: @@ -571,7 +520,7 @@ paths: description: Returns the list of all query templates of the current user operationId: getQueryTemplateList parameters: - - name: skipValidation + - name: skip-validation in: query description: If true, do not validate the query and do not include a list of invalid terms required: false @@ -585,12 +534,11 @@ paths: application/json: schema: items: - $ref: '#/components/schemas/QueryTemplateListItem' + $ref: "#/components/schemas/QueryTemplateListItem" 401: description: Unauthorized - please login first - content: { } security: - - feasibility_auth: + - dataportal_auth: - user /query/template/{queryTemplateId}: get: @@ -607,7 +555,7 @@ paths: schema: type: integer format: int64 - - name: skipValidation + - name: skip-validation in: query description: If true, do not validate the query and do not include a list of invalid terms required: false @@ -621,15 +569,13 @@ paths: application/json: schema: items: - $ref: '#/components/schemas/QueryTemplate' + $ref: "#/components/schemas/QueryTemplate" 401: description: Unauthorized - please login first - content: { } 404: description: Query not found (or user has no access) - content: { } security: - - feasibility_auth: + - dataportal_auth: - user put: tags: @@ -650,20 +596,17 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/QueryTemplate' + $ref: "#/components/schemas/QueryTemplate" required: true responses: 200: description: OK - content: {} 401: description: Unauthorized - please login first - content: { } 404: description: Query not found (or user has no access) - content: { } security: - - feasibility_auth: + - dataportal_auth: - user delete: tags: @@ -682,187 +625,26 @@ paths: responses: 200: description: OK - content: {} 401: description: Unauthorized - please login first - content: { } 404: description: Query not found (or user has no access) - content: { } security: - - feasibility_auth: + - dataportal_auth: - user - /terminology/categories: - get: - tags: - - terminology - summary: Get the list of top-level categories for the UI Tree - description: Get the list of top-level categories for the UI Tree - operationId: "getCategories" - responses: - "200": - description: "OK" - content: - application/json: - schema: - type: "array" - items: - $ref: "#/components/schemas/CategoryEntry" - /terminology/entries/{nodeId}: - get: - tags: - - terminology - summary: Get the ui tree information for a node - operationId: "getEntry" - parameters: - - name: "nodeId" - in: "path" - required: true - schema: - type: "string" - format: "uuid" - responses: - 200: - description: "OK" - content: - application/json: - schema: - $ref: "#/components/schemas/TerminologyEntry" - 404: - description: Node not found - content: {} - /terminology/entries: - get: - tags: - - terminology - summary: "Search for termcodes containing the search string" - operationId: "search" - parameters: - - name: "query" - in: "query" - required: true - schema: - type: "string" - - name: "categoryId" - in: "query" - required: false - schema: - type: "string" - format: "uuid" - responses: - 200: - description: "OK" - content: - application/json: - schema: - type: "array" - items: - $ref: "#/components/schemas/TerminologyEntry" - /terminology/{contextualizedTermcodeId}/ui_profile: - get: - tags: - - terminology - summary: "Get a UI profile for a given concept termcode and context termcode" - operationId: "getUiProfile" - parameters: - - name: contextualizedTermcodeId - in: path - description: A hashed value (UUID v3), calculated from context and termcode with a predefined namespace - required: true - schema: - type: "string" - example: 43159902-7171-30cf-ab7e-ee36986c37c6 - responses: - 200: - description: "OK" - content: - application/json: - schema: - type: "string" - example: - name: Prozedur - time_restriction_allowed: true - - 404: - description: No matching ui profile found - content: {} - /terminology/{contextualizedTermcodeId}/mapping: - get: - tags: - - terminology - summary: "Get the mapping for a given concept termcode and context termcode" - operationId: "getMapping" - parameters: - - name: contextualizedTermcodeId - in: path - description: A hashed value (UUID v3), calculated from context and termcode with a predefined namespace - required: true - schema: - type: "string" - example: 43159902-7171-30cf-ab7e-ee36986c37c6 - responses: - 200: - description: "OK" - content: - application/json: - schema: - type: "string" - example: - name: Age - resource_type: Patient - valueSearchParameter: birthdate - valueType: Age - - 404: - description: No matching mapping found - content: {} - /terminology/criteria-set/intersect: - post: - tags: - - terminology - summary: "Submit a list of termcodes with their contexts and check if they are contained in a criteria set defined by the query parameter" - operationId: "intersect" - parameters: - - name: criteriaSetUrl - in: query - description: The Canonical URL of the criteria set to check in - required: true - schema: - type: "string" - example: http://fhir.de/CriteriaSet/bfarm/icd-10-gm - requestBody: - description: A list of ContextTermCodeHashes - required: true - content: - application/json: - schema: - type: array - items: - type: string - example: ["b8318334-7039-33aa-8ed0-85898fffa40b", "a57f98cf-e71d-3695-ae88-59d2c651bf0f", "430654d1-a7df-3c42-a33a-7210ef372bfd"] - responses: - 200: - description: Ok, return the filtered list of contextualized termcodes that are in the criteria set provided via query param - content: - application/json: - schema: - type: array - items: - type: string - example: ["b8318334-7039-33aa-8ed0-85898fffa40b", "a57f98cf-e71d-3695-ae88-59d2c651bf0f"] /terminology/search/filter: get: tags: - terminology - summary: "Get the list of available filters" - operationId: "getFilters" + summary: Get the list of available filters + operationId: getFilters responses: 200: description: Ok, return the list of available filters content: application/json: schema: - type: "array" + type: array items: $ref: "#/components/schemas/Filter" /terminology/systems: @@ -870,7 +652,7 @@ paths: tags: - terminology summary: Get the mapping of system urls to display names - operationId: "getTerminologySystems" + operationId: getTerminologySystems responses: 200: description: System mapping file found and readable @@ -882,14 +664,14 @@ paths: get: tags: - terminology - summary: "Parametrized search in the configured elastic search service." - operationId: "searchEntry" + summary: Parametrized search in the configured elastic search service. + operationId: searchEntry parameters: - name: searchterm in: query description: The term to search for. In case of an empty searchterm, return the first page of the whole index, sorted alphabetically schema: - type: "string" + type: string example: Diabetes Mellitus - name: contexts in: query @@ -960,8 +742,8 @@ paths: get: tags: - terminology - summary: "Get the detailed entry for a criterion, containing children and translations" - operationId: "getEntryById" + summary: Get the detailed entry for a criterion, containing children and translations + operationId: getEntryById parameters: - name: id in: path @@ -976,7 +758,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ElasticSearchResultEntryWithRelations' + $ref: "#/components/schemas/ElasticSearchResultEntryWithRelations" 401: description: Unauthorized - please login first 403: @@ -987,9 +769,9 @@ paths: get: tags: - terminology - summary: "Get the detailed entry for a criterion, containing children and translations" - description: "This should be in the getEntryById call, but for better distinction between the return values it is listed seperately here" - operationId: "getEntriesById" + summary: Get the detailed entry for a criterion, containing children and translations + description: This should be in the getEntryById call, but for better distinction between the return values it is listed seperately here + operationId: getEntriesById parameters: - name: id in: path @@ -1004,7 +786,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/ElasticSearchResultEntry' + $ref: "#/components/schemas/ElasticSearchResultEntry" 401: description: Unauthorized - please login first 403: @@ -1015,32 +797,32 @@ paths: get: tags: - terminology - summary: "Get the profile data for criteria, containing uiProfile context and termCodes" - description: "This should return all the information needed to build criteria in the frontend." - operationId: "getEntriesByIdsWithDetail" + summary: Get the profile data for criteria, containing uiProfile context and termCodes + description: This should return all the information needed to build criteria in the frontend. + operationId: getEntriesByIdsWithDetail parameters: - name: ids in: query style: form explode: false - description: "A comma-separated list of IDs (contextualized termcode hashes) of the entries that shall be retrieved." + description: A comma-separated list of IDs (contextualized termcode hashes) of the entries that shall be retrieved. required: true schema: type: string - example: "203e04cd-4f0a-321b-b1ad-9ec6d211e0a8,203e04cd-4f0a-321b-b1ad-9ec6d211e0a9" + example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8,203e04cd-4f0a-321b-b1ad-9ec6d211e0a9 responses: 200: - description: "Entries found. May contain empty entries if some were not found." + description: Entries found. May contain empty entries if some were not found. content: application/json: schema: type: array items: - $ref: '#/components/schemas/CriteriaProfileData' + $ref: "#/components/schemas/CriteriaProfileData" 401: - description: "Unauthorized - please login first." + description: Unauthorized - please login first. 403: - description: "Forbidden - insufficient access rights." + description: Forbidden - insufficient access rights. /codeable-concept/entry/search: get: tags: @@ -1052,7 +834,7 @@ paths: in: query description: The term to search for. In case of an empty searchterm, return the first page of the whole index, sorted alphabetically schema: - type: "string" + type: string example: Diabetes Mellitus - name: value-sets in: query @@ -1120,7 +902,7 @@ paths: - dse summary: Get the complete DSE profile tree description: This can not be filtered or paginated or anything since for now it is rather small. All filtering will be done in the frontend for now. Might change if problems with that approach occur. - operationId: "getProfileTree" + operationId: getProfileTree responses: 200: description: Profile tree file found and readable @@ -1134,7 +916,7 @@ paths: - dse summary: Get the profile data for the submitted ids description: Get profile data for all submitted ids. If a profile is not found, an error message will be supplied. - operationId: "getProfileData" + operationId: getProfileData responses: 200: description: Success, returning profiles - may contain profiles with error messages @@ -1177,7 +959,7 @@ components: type: string createdAt: type: string - format: 'date-time' + format: date-time totalNumberOfPatients: type: integer isValid: @@ -1276,11 +1058,13 @@ components: label: type: string description: The 'name' of the query. Is assigned by the user via GUI. - example: my-query-1 + examples: + - my-query-1 comment: type: string description: A more detailed information about the query. Is also assigned by the user via GUI. - example: I wanted to see how many patients I could find for my study XYZ + examples: + - I wanted to see how many patients I could find for my study XYZ lastModified: type: string format: date-time @@ -1301,11 +1085,13 @@ components: label: type: string description: The 'name' of the query. Is assigned by the user via GUI. - example: my-query-1 + examples: + - my-query-1 comment: type: string description: A more detailed information about the query. Is also assigned by the user via GUI. - example: I wanted to see how many patients I could find for my study XYZ + examples: + - I wanted to see how many patients I could find for my study XYZ content: $ref: "#/components/schemas/StructuredQuery" lastModified: @@ -1324,10 +1110,12 @@ components: type: string format: uri description: The json schema version - example: http://to_be_decided.com/draft-1/schema# + examples: + - http://to_be_decided.com/draft-1/schema# display: type: string - example: foobar + examples: + - foobar inclusionCriteria: type: array items: @@ -1344,16 +1132,19 @@ components: label: type: string description: The 'name' of the query. Is assigned by the user via GUI. - example: my-query-1 + examples: + - my-query-1 comment: type: string description: A more detailed information about the query. Is also assigned by the user via GUI. - example: I wanted to see how many patients I could find for my study XYZ + examples: + - I wanted to see how many patients I could find for my study XYZ totalNumberOfPatients: type: integer format: int64 description: The number of results that were found for this query. - example: 12345 + examples: + - 12345 SavedQuerySlots: type: object required: @@ -1363,11 +1154,13 @@ components: used: type: integer description: The amount of used saved query slots for a user. - example: 2 + examples: + - 2 total: type: integer description: The total amount of saved query slots per user. - example: 10 + examples: + - 10 TermCode: description: The termCode defines a concept based on a coding system (i.e. LOINC). The triplet of code, system and version identify the concept. type: object @@ -1378,16 +1171,20 @@ components: properties: code: type: string - example: 119373006 + examples: + - 119373006 system: type: string - example: http://snomed.info/sct + examples: + - http://snomed.info/sct version: type: string - example: http://snomed.info/sct/900000000000207008/version/20210731 + examples: + - http://snomed.info/sct/900000000000207008/version/20210731 display: type: string - example: Amniotic fluid specimen (specimen) + examples: + - Amniotic fluid specimen (specimen) beforeDate: type: string format: date-time @@ -1477,44 +1274,6 @@ components: type: array items: $ref: "#/components/schemas/Criterion" - CategoryEntry: - type: "object" - required: - - catId - - display - properties: - catId: - type: "string" - format: "uuid" - display: - type: "string" - example: "Diagnose" - TerminologyEntry: - type: "object" - properties: - id: - type: "string" - format: "uuid" - context: - $ref: "#/components/schemas/TermCode" - termCodes: - type: "array" - items: - $ref: "#/components/schemas/TermCode" - termCode: - $ref: "#/components/schemas/TermCode" - children: - type: "array" - items: - $ref: "#/components/schemas/TerminologyEntry" - leaf: - type: "boolean" - selectable: - type: "boolean" - display: - type: "string" - root: - type: "boolean" ContextualizedTermCodeList: type: object properties: @@ -1537,16 +1296,19 @@ components: properties: code: type: string - example: VAL-20001 + examples: + - VAL-20001 detail: type: string - example: The combination of context and termcode(s) is not found. + examples: + - The combination of context and termcode(s) is not found. ElasticSearchResult: type: object properties: totalHits: type: integer - example: 42 + examples: + - 42 results: type: array items: @@ -1556,29 +1318,36 @@ components: properties: name: type: string - example: Diabetes Mellitus + examples: + - Diabetes Mellitus contextualizedTermcodeHash: type: string format: uuid - example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 + examples: + - 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 availability: description: Not sure if we want this as numeric value, percentage or just boolean? type: integer minimum: 0 maximum: 100 - example: 94 + examples: + - 94 context: type: string - example: Diagnosis + examples: + - Diagnosis terminology: type: string - example: icd-10 + examples: + - icd-10 termcode: type: string - example: E10-E14 + examples: + - E10-E14 kdsModule: type: string - example: Condition + examples: + - Condition translations: type: array items: @@ -1596,69 +1365,83 @@ components: properties: name: type: string - example: Diabetes Mellitus + examples: + - Diabetes Mellitus contextualizedTermcodeHash: type: string format: uuid - example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 + examples: + - 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 availability: description: Not sure if we want this as numeric value, percentage or just boolean? type: integer minimum: 0 - example: 119578 + examples: + - 119578 context: type: string - example: Diagnosis + examples: + - Diagnosis terminology: type: string - example: icd-10 + examples: + - icd-10 termcode: type: string - example: E10-E14 + examples: + - E10-E14 kdsModule: type: string - example: Condition + examples: + - Condition ElasticSearchTranslationEntry: type: object properties: lang: type: string - example: en + examples: + - en value: type: string - example: Diabetes Mellitus + examples: + - Diabetes Mellitus ElasticSearchRelationEntry: type: object properties: name: type: string - example: Endokrine, Ernährungs- und Stoffwechselerkrankungen + examples: + - Endokrine, Ernährungs- und Stoffwechselerkrankungen contextualizedTermcodeHash: type: string - example: c55d0d62-6c47-30b0-94b2-afa383ce35f7 + examples: + - c55d0d62-6c47-30b0-94b2-afa383ce35f7 Filter: type: object properties: name: type: string - example: Terminology + examples: + - Terminology values: type: array items: $ref: "#/components/schemas/FilterValue" - example: + examples: - ICD10 - SNOMED - LOINC FilterValue: type: string - example: "icd10" + examples: + - icd10 CodeableConceptSearchResult: type: object properties: totalHits: type: integer - example: 42 + examples: + - 42 results: type: array items: @@ -1688,10 +1471,12 @@ components: $ref: "#/components/schemas/AttributeDefinition" name: type: string - example: "Diagnose" + examples: + - Diagnose timeRestrictionAllowed: type: boolean - example: false + examples: + - false valueDefinition: type: string AttributeDefinition: @@ -1705,25 +1490,32 @@ components: $ref: "#/components/schemas/TermCode" max: type: number - example: null + examples: + - null min: type: number - example: null + examples: + - null optional: type: boolean - example: true + examples: + - true precision: type: number - example: 1 + examples: + - 1 referencedCriteriaSet: type: string - example: "http://fdpg.mii.cds/CriteriaSet/Diagnose/icd-10-gm" + examples: + - http://fdpg.mii.cds/CriteriaSet/Diagnose/icd-10-gm referencedValueSet: type: string - example: "http://hl7.org/fhir/sid/icd-o-3" + examples: + - http://hl7.org/fhir/sid/icd-o-3 type: type: string - example: "reference" + examples: + - reference TerminologySystems: type: object required: @@ -1733,52 +1525,64 @@ components: url: type: string format: uri - example: http://fhir.de/CodeSystem/bfarm/ops + examples: + - http://fhir.de/CodeSystem/bfarm/ops name: type: string - example: OPS + examples: + - OPS ProfileTreeNode: type: object properties: id: type: string format: uuid - example: aee6afe1-3b96-4449-85cd-be8046ccbb84 + examples: + - aee6afe1-3b96-4449-85cd-be8046ccbb84 children: type: array items: $ref: "#/components/schemas/ProfileTreeNode" name: type: string - example: ProfileObservationLaboruntersuchung + examples: + - ProfileObservationLaboruntersuchung display: type: string - example: Profile - Observation - Laboruntersuchung + examples: + - Profile - Observation - Laboruntersuchung module: type: string - example: modul-labor + examples: + - modul-labor url: type: string format: uri - example: https://www.medizininformatik-initiative.de/fhir/core/modul-labor/StructureDefinition/ObservationLab + examples: + - https://www.medizininformatik-initiative.de/fhir/core/modul-labor/StructureDefinition/ObservationLab leaf: type: boolean - example: true + examples: + - true selectable: type: boolean - example: false + examples: + - false ProfileDataField: type: object properties: id: type: string - example: Observation.value[x]:valueQuantity.id + examples: + - Observation.value[x]:valueQuantity.id display: type: string - example: Unique id for inter-element referencing + examples: + - Unique id for inter-element referencing name: type: string - example: id + examples: + - id children: type: array items: @@ -1788,27 +1592,33 @@ components: properties: type: type: string - example: token + examples: + - token name: type: string - example: code + examples: + - code ui_type: type: string - example: code + examples: + - code referencedCriteriaSet: type: string format: uri - example: http://fdpg.mii.cds/CriteriaSet/Diagnose/icd-10-gm + examples: + - http://fdpg.mii.cds/CriteriaSet/Diagnose/icd-10-gm ProfileDataEntry: type: object properties: url: type: string format: uri - example: https://www.medizininformatik-initiative.de/fhir/core/modul-labor/StructureDefinition/ObservationLab + examples: + - https://www.medizininformatik-initiative.de/fhir/core/modul-labor/StructureDefinition/ObservationLab display: type: string - example: Profile - Observation - Laboruntersuchung + examples: + - Profile - Observation - Laboruntersuchung fields: type: array items: @@ -1819,20 +1629,22 @@ components: $ref: "#/components/schemas/ProfileDataFilter" errorCode: type: string - example: "TBD-000" + examples: + - TBD-000 errorCause: type: string - example: Profile entry not found + examples: + - Profile entry not found ProfileDataList: type: array items: $ref: "#/components/schemas/ProfileDataEntry" securitySchemes: - feasibility_auth: + dataportal_auth: type: oauth2 flows: implicit: authorizationUrl: http://to.be.defined/auth scopes: - user: Feasibility user role - admin: Feasibility admin role + user: Dataportal user role + admin: Dataportal admin role diff --git a/src/test/java/de/numcodex/feasibility_gui_backend/dse/v3/DseRestControllerIT.java b/src/test/java/de/numcodex/feasibility_gui_backend/dse/v4/DseRestControllerIT.java similarity index 94% rename from src/test/java/de/numcodex/feasibility_gui_backend/dse/v3/DseRestControllerIT.java rename to src/test/java/de/numcodex/feasibility_gui_backend/dse/v4/DseRestControllerIT.java index 4fe233e3..cc9a2669 100644 --- a/src/test/java/de/numcodex/feasibility_gui_backend/dse/v3/DseRestControllerIT.java +++ b/src/test/java/de/numcodex/feasibility_gui_backend/dse/v4/DseRestControllerIT.java @@ -1,4 +1,4 @@ -package de.numcodex.feasibility_gui_backend.dse.v3; +package de.numcodex.feasibility_gui_backend.dse.v4; import com.fasterxml.jackson.databind.ObjectMapper; import de.numcodex.feasibility_gui_backend.dse.DseService; @@ -48,7 +48,7 @@ class DseRestControllerIT { private RateLimitingInterceptor rateLimitingInterceptor; @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testGetProfileTree_succeedsWith200() throws Exception { doReturn(jsonUtil.readValue(new URL("file:src/test/resources/ontology/dse/profile_tree.json"), DseProfileTreeNode.class)).when(dseService).getProfileTree(); @@ -61,7 +61,7 @@ public void testGetProfileTree_succeedsWith200() throws Exception { } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testGetProfileTree_failsOnFileNotFound() throws Exception { doReturn(null).when(dseService).getProfileTree(); @@ -70,7 +70,7 @@ public void testGetProfileTree_failsOnFileNotFound() throws Exception { } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") void testGetProfileData_succeedsWith200OnFoundProfile() throws Exception { doReturn(List.of(createDummyDseProfileEntry())).when(dseService).getProfileData(anyList()); @@ -81,7 +81,7 @@ void testGetProfileData_succeedsWith200OnFoundProfile() throws Exception { } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") void testGetProfileData_succeedsWith200OnNoFoundProfile() throws Exception { doReturn(List.of()).when(dseService).getProfileData(anyList()); diff --git a/src/test/java/de/numcodex/feasibility_gui_backend/query/ratelimiting/RateLimitingInterceptorIT.java b/src/test/java/de/numcodex/feasibility_gui_backend/query/ratelimiting/RateLimitingInterceptorIT.java index b4e2bf67..04b9b05a 100644 --- a/src/test/java/de/numcodex/feasibility_gui_backend/query/ratelimiting/RateLimitingInterceptorIT.java +++ b/src/test/java/de/numcodex/feasibility_gui_backend/query/ratelimiting/RateLimitingInterceptorIT.java @@ -18,7 +18,7 @@ import de.numcodex.feasibility_gui_backend.query.api.validation.StructuredQueryValidatorSpringConfig; import de.numcodex.feasibility_gui_backend.query.persistence.UserBlacklistRepository; import de.numcodex.feasibility_gui_backend.query.result.ResultLine; -import de.numcodex.feasibility_gui_backend.query.v3.QueryHandlerRestController; +import de.numcodex.feasibility_gui_backend.query.v4.QueryHandlerRestController; import de.numcodex.feasibility_gui_backend.terminology.validation.StructuredQueryValidation; import java.net.URI; @@ -76,7 +76,7 @@ public class RateLimitingInterceptorIT { @BeforeEach void setupMockBehaviour() throws InvalidAuthenticationException { doReturn(true).when(authenticationHelper) - .hasAuthority(any(Authentication.class), eq("FEASIBILITY_TEST_USER")); + .hasAuthority(any(Authentication.class), eq("DATAPORTAL_TEST_USER")); doReturn(createTestQueryResult(ResultDetail.SUMMARY)).when(queryHandlerService) .getQueryResult(any(Long.class), eq(ResultDetail.SUMMARY)); doReturn(createTestQueryResult(ResultDetail.DETAILED)).when(queryHandlerService) @@ -102,13 +102,13 @@ public void testGetResult_SucceedsOnFirstCall(ResultDetail resultDetail) throws } doReturn(isAdmin).when(authenticationHelper) - .hasAuthority(any(Authentication.class), eq("FEASIBILITY_TEST_ADMIN")); + .hasAuthority(any(Authentication.class), eq("DATAPORTAL_TEST_ADMIN")); doReturn(authorName).when(queryHandlerService).getAuthorId(any(Long.class)); mockMvc .perform( get(URI.create(requestUri)).with(csrf()) - .with(user(authorName).password("pass").roles("FEASIBILITY_TEST_USER")) + .with(user(authorName).password("pass").roles("DATAPORTAL_TEST_USER")) ) .andExpect(status().isOk()); } @@ -130,20 +130,20 @@ public void testGetResult_FailsOnImmediateSecondCall(ResultDetail resultDetail) } doReturn(false).when(authenticationHelper) - .hasAuthority(any(Authentication.class), eq("FEASIBILITY_TEST_ADMIN")); + .hasAuthority(any(Authentication.class), eq("DATAPORTAL_TEST_ADMIN")); doReturn(authorName).when(queryHandlerService).getAuthorId(any(Long.class)); mockMvc .perform( get(requestUri).with(csrf()) - .with(user(authorName).password("pass").roles("FEASIBILITY_TEST_USER")) + .with(user(authorName).password("pass").roles("DATAPORTAL_TEST_USER")) ) .andExpect(status().isOk()); mockMvc .perform( get(URI.create(requestUri)).with(csrf()) - .with(user(authorName).password("pass").roles("FEASIBILITY_TEST_USER")) + .with(user(authorName).password("pass").roles("DATAPORTAL_TEST_USER")) ) .andExpect(status().isTooManyRequests()); } @@ -164,13 +164,13 @@ public void testGetResult_SucceedsOnDelayedSecondCall(ResultDetail resultDetail) } doReturn(false).when(authenticationHelper) - .hasAuthority(any(Authentication.class), eq("FEASIBILITY_TEST_ADMIN")); + .hasAuthority(any(Authentication.class), eq("DATAPORTAL_TEST_ADMIN")); doReturn(authorName).when(queryHandlerService).getAuthorId(any(Long.class)); mockMvc .perform( get(requestUri).with(csrf()) - .with(user(authorName).password("pass").roles("FEASIBILITY_TEST_USER")) + .with(user(authorName).password("pass").roles("DATAPORTAL_TEST_USER")) ) .andExpect(status().isOk()); @@ -179,7 +179,7 @@ public void testGetResult_SucceedsOnDelayedSecondCall(ResultDetail resultDetail) mockMvc .perform( get(URI.create(PATH_API + "/query/1" + WebSecurityConfig.PATH_SUMMARY_RESULT)).with(csrf()) - .with(user(authorName).password("pass").roles("FEASIBILITY_TEST_USER")) + .with(user(authorName).password("pass").roles("DATAPORTAL_TEST_USER")) ) .andExpect(status().isOk()); } @@ -199,14 +199,14 @@ public void testGetResult_SucceedsOnImmediateMultipleCallsAsAdmin(ResultDetail r } doReturn(true).when(authenticationHelper) - .hasAuthority(any(Authentication.class), eq("FEASIBILITY_TEST_ADMIN")); + .hasAuthority(any(Authentication.class), eq("DATAPORTAL_TEST_ADMIN")); doReturn(authorName).when(queryHandlerService).getAuthorId(any(Long.class)); for (int i = 0; i < 10; ++i) { mockMvc .perform( get(requestUri).with(csrf()) - .with(user(authorName).password("pass").roles("FEASIBILITY_TEST_ADMIN")) + .with(user(authorName).password("pass").roles("DATAPORTAL_TEST_ADMIN")) ) .andExpect(status().isOk()); } @@ -229,13 +229,13 @@ public void testGetResult_SucceedsOnImmediateSecondCallAsOtherUser(ResultDetail } doReturn(false).when(authenticationHelper) - .hasAuthority(any(Authentication.class), eq("FEASIBILITY_TEST_ADMIN")); + .hasAuthority(any(Authentication.class), eq("DATAPORTAL_TEST_ADMIN")); doReturn(authorName).when(queryHandlerService).getAuthorId(any(Long.class)); mockMvc .perform( get(requestUri).with(csrf()) - .with(user(authorName).password("pass").roles("FEASIBILITY_TEST_USER")) + .with(user(authorName).password("pass").roles("DATAPORTAL_TEST_USER")) ) .andExpect(status().isOk()); @@ -245,7 +245,7 @@ public void testGetResult_SucceedsOnImmediateSecondCallAsOtherUser(ResultDetail mockMvc .perform( get(URI.create(PATH_API + "/query/1" + WebSecurityConfig.PATH_SUMMARY_RESULT)).with(csrf()) - .with(user(authorName).password("pass").roles("FEASIBILITY_TEST_USER")) + .with(user(authorName).password("pass").roles("DATAPORTAL_TEST_USER")) ) .andExpect(status().isOk()); } @@ -256,13 +256,13 @@ public void testGetDetailedObfuscatedResult_FailsOnLimitExceedingCall() throws E var requestUri = PATH_API + "/query/1" + WebSecurityConfig.PATH_DETAILED_OBFUSCATED_RESULT; doReturn(false).when(authenticationHelper) - .hasAuthority(any(Authentication.class), eq("FEASIBILITY_TEST_ADMIN")); + .hasAuthority(any(Authentication.class), eq("DATAPORTAL_TEST_ADMIN")); doReturn(authorName).when(queryHandlerService).getAuthorId(any(Long.class)); mockMvc .perform( get(requestUri).with(csrf()) - .with(user(authorName).password("pass").roles("FEASIBILITY_TEST_USER")) + .with(user(authorName).password("pass").roles("DATAPORTAL_TEST_USER")) ) .andExpect(status().isOk()); @@ -272,7 +272,7 @@ public void testGetDetailedObfuscatedResult_FailsOnLimitExceedingCall() throws E mockMvc .perform( get(URI.create(requestUri)).with(csrf()) - .with(user(authorName).password("pass").roles("FEASIBILITY_TEST_USER")) + .with(user(authorName).password("pass").roles("DATAPORTAL_TEST_USER")) ) .andExpect(status().isTooManyRequests()); @@ -281,7 +281,7 @@ public void testGetDetailedObfuscatedResult_FailsOnLimitExceedingCall() throws E mockMvc .perform( get(requestUri).with(csrf()) - .with(user(authorName).password("pass").roles("FEASIBILITY_TEST_USER")) + .with(user(authorName).password("pass").roles("DATAPORTAL_TEST_USER")) ) .andExpect(status().isOk()); } diff --git a/src/test/java/de/numcodex/feasibility_gui_backend/query/translation/CqlQueryTranslatorIT.java b/src/test/java/de/numcodex/feasibility_gui_backend/query/translation/CqlQueryTranslatorIT.java index e39ca266..3a2d0474 100644 --- a/src/test/java/de/numcodex/feasibility_gui_backend/query/translation/CqlQueryTranslatorIT.java +++ b/src/test/java/de/numcodex/feasibility_gui_backend/query/translation/CqlQueryTranslatorIT.java @@ -34,8 +34,8 @@ properties = { "app.cqlTranslationEnabled=true", "app.fhirTranslationEnabled=false", - "app.mappingsFile=./ontology/codex-term-code-mapping.json", - "app.conceptTreeFile=./ontology/codex-code-tree.json" + "app.mappingsFile=./ontology/dataportal-term-code-mapping.json", + "app.conceptTreeFile=./ontology/dataportal-code-tree.json" } ) @SuppressWarnings("NewClassNamingConvention") diff --git a/src/test/java/de/numcodex/feasibility_gui_backend/query/translation/FhirQueryTranslatorIT.java b/src/test/java/de/numcodex/feasibility_gui_backend/query/translation/FhirQueryTranslatorIT.java index a15c8237..11c01d55 100644 --- a/src/test/java/de/numcodex/feasibility_gui_backend/query/translation/FhirQueryTranslatorIT.java +++ b/src/test/java/de/numcodex/feasibility_gui_backend/query/translation/FhirQueryTranslatorIT.java @@ -44,8 +44,8 @@ properties = { "app.cqlTranslationEnabled=true", "app.fhirTranslationEnabled=false", - "app.mappingsFile=./ontology/codex-term-code-mapping.json", - "app.conceptTreeFile=./ontology/codex-code-tree.json" + "app.mappingsFile=./ontology/dataportal-term-code-mapping.json", + "app.conceptTreeFile=./ontology/dataportal-code-tree.json" } ) @Testcontainers @@ -63,8 +63,8 @@ public class FhirQueryTranslatorIT { @Container private final GenericContainer flare = new GenericContainer<>(DockerImageName.parse("ghcr.io/num-codex/codex-flare:0.0.8")) .withExposedPorts(5000) - .withFileSystemBind("ontology/codex-code-tree.json", "/opt/flare/src/query_parser/codex/codex-code-tree.json", READ_ONLY) - .withFileSystemBind("ontology/codex-term-code-mapping.json", "/opt/flare/src/query_parser/codex/codex-mapping.json", READ_ONLY) + .withFileSystemBind("ontology/dataportal-code-tree.json", "/opt/flare/src/query_parser/codex/codex-code-tree.json", READ_ONLY) + .withFileSystemBind("ontology/dataportal-term-code-mapping.json", "/opt/flare/src/query_parser/codex/codex-mapping.json", READ_ONLY) .waitingFor(Wait.forHttp("/") .forStatusCodeMatching(c -> c >= 200 && c <= 500)) .withStartupAttempts(5); diff --git a/src/test/java/de/numcodex/feasibility_gui_backend/query/v3/QueryHandlerRestControllerIT.java b/src/test/java/de/numcodex/feasibility_gui_backend/query/v4/QueryHandlerRestControllerIT.java similarity index 92% rename from src/test/java/de/numcodex/feasibility_gui_backend/query/v3/QueryHandlerRestControllerIT.java rename to src/test/java/de/numcodex/feasibility_gui_backend/query/v4/QueryHandlerRestControllerIT.java index 35c79ef5..fc417c47 100644 --- a/src/test/java/de/numcodex/feasibility_gui_backend/query/v3/QueryHandlerRestControllerIT.java +++ b/src/test/java/de/numcodex/feasibility_gui_backend/query/v4/QueryHandlerRestControllerIT.java @@ -1,4 +1,4 @@ -package de.numcodex.feasibility_gui_backend.query.v3; +package de.numcodex.feasibility_gui_backend.query.v4; import de.numcodex.feasibility_gui_backend.config.WebSecurityConfig; import de.numcodex.feasibility_gui_backend.query.QueryNotFoundException; import de.numcodex.feasibility_gui_backend.query.api.*; @@ -116,7 +116,7 @@ void initTest() throws Exception { } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testRunQueryEndpoint_FailsOnInvalidStructuredQueryWith400() throws Exception { var testQuery = StructuredQuery.builder().build(); @@ -127,7 +127,7 @@ public void testRunQueryEndpoint_FailsOnInvalidStructuredQueryWith400() throws E } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER", username = "test") + @WithMockUser(roles = "DATAPORTAL_TEST_USER", username = "test") public void testRunQueryEndpoint_SucceedsOnValidStructuredQueryWith201() throws Exception { StructuredQuery testQuery = createValidStructuredQuery(); var annotatedQuery = createValidAnnotatedStructuredQuery(false); @@ -148,7 +148,7 @@ public void testRunQueryEndpoint_SucceedsOnValidStructuredQueryWith201() throws } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER", username = "test") + @WithMockUser(roles = "DATAPORTAL_TEST_USER", username = "test") public void testRunQueryEndpoint_FailsOnDownstreamServiceError() throws Exception { StructuredQuery testQuery = createValidStructuredQuery(); var annotatedQuery = createValidAnnotatedStructuredQuery(false); @@ -169,7 +169,7 @@ public void testRunQueryEndpoint_FailsOnDownstreamServiceError() throws Exceptio } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER", username = "test") + @WithMockUser(roles = "DATAPORTAL_TEST_USER", username = "test") public void testRunQueryEndpoint_FailsOnSoftQuotaExceeded() throws Exception { StructuredQuery testQuery = createValidStructuredQuery(); var annotatedQuery = createValidAnnotatedStructuredQuery(false); @@ -188,7 +188,7 @@ public void testRunQueryEndpoint_FailsOnSoftQuotaExceeded() throws Exception { } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER", username = "test") + @WithMockUser(roles = "DATAPORTAL_TEST_USER", username = "test") public void testValidateQueryEndpoint_SucceedsOnValidQuery() throws Exception { StructuredQuery testQuery = createValidStructuredQuery(); var annotatedQuery = createValidAnnotatedStructuredQuery(false); @@ -204,7 +204,7 @@ public void testValidateQueryEndpoint_SucceedsOnValidQuery() throws Exception { } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testValidateQueryEndpoint_SucceedsDespiteInvalidCriteriaWith200() throws Exception { StructuredQuery testQuery = createValidStructuredQuery(); var annotatedQuery = createValidAnnotatedStructuredQuery(true); @@ -220,7 +220,7 @@ public void testValidateQueryEndpoint_SucceedsDespiteInvalidCriteriaWith200() th } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER", username = "test") + @WithMockUser(roles = "DATAPORTAL_TEST_USER", username = "test") public void testRunQueryEndpoint_FailsOnBeingBlacklistedWith403() throws Exception { StructuredQuery testQuery = createValidStructuredQuery(); var annotatedQuery = createValidAnnotatedStructuredQuery(false); @@ -244,7 +244,7 @@ public void testRunQueryEndpoint_FailsOnBeingBlacklistedWith403() throws Excepti } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER", username = "test") + @WithMockUser(roles = "DATAPORTAL_TEST_USER", username = "test") public void testRunQueryEndpoint_FailsOnExceedingHardLimitWith403() throws Exception { StructuredQuery testQuery = createValidStructuredQuery(); var annotatedQuery = createValidAnnotatedStructuredQuery(false); @@ -264,12 +264,12 @@ public void testRunQueryEndpoint_FailsOnExceedingHardLimitWith403() throws Excep } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER", "FEASIBILITY_TEST_POWER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER", "DATAPORTAL_TEST_POWER"}, username = "test") public void testRunQueryEndpoint_SucceedsOnExceedingHardlimitAsPowerUserWith201() throws Exception { StructuredQuery testQuery = createValidStructuredQuery(); var annotatedQuery = createValidAnnotatedStructuredQuery(false); - doReturn(true).when(authenticationHelper).hasAuthority(any(Authentication.class), eq("FEASIBILITY_TEST_POWER")); + doReturn(true).when(authenticationHelper).hasAuthority(any(Authentication.class), eq("DATAPORTAL_TEST_POWER")); doReturn((long)quotaHardCreateAmount).when(queryHandlerService).getAmountOfQueriesByUserAndInterval(any(String.class), eq(quotaHardCreateIntervalMinutes)); doReturn((long)(quotaSoftCreateAmount - 1)).when(queryHandlerService).getAmountOfQueriesByUserAndInterval(any(String.class), eq(quotaSoftCreateIntervalMinutes)); doReturn(Mono.just(1L)).when(queryHandlerService).runQuery(any(StructuredQuery.class), eq("test")); @@ -288,13 +288,13 @@ public void testRunQueryEndpoint_SucceedsOnExceedingHardlimitAsPowerUserWith201( } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") public void testGetQueryList_SucceedsWithValidation() throws Exception { long queryId = 1; doReturn(List.of(createValidQuery(queryId))).when(queryHandlerService).getQueryListForAuthor(any(String.class), any(Boolean.class)); doReturn(List.of(createValidQueryListEntry(queryId, false))).when(queryHandlerService).convertQueriesToQueryListEntries(anyList(), any(Boolean.class)); - mockMvc.perform(get(URI.create(PATH_API + PATH_QUERY)).with(csrf()).param("skipValidation", "false")) + mockMvc.perform(get(URI.create(PATH_API + PATH_QUERY)).with(csrf()).param("skip-validation", "false")) .andExpect(status().isOk()) .andExpect(jsonPath("$[0].id").value(queryId)) .andExpect(jsonPath("$[0].isValid").exists()) @@ -302,20 +302,20 @@ public void testGetQueryList_SucceedsWithValidation() throws Exception { } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") public void testGetQueryList_SucceedsWithoutValidation() throws Exception { long queryId = 1; doReturn(List.of(createValidQuery(queryId))).when(queryHandlerService).getQueryListForAuthor(any(String.class), any(Boolean.class)); doReturn(List.of(createValidQueryListEntry(queryId, true))).when(queryHandlerService).convertQueriesToQueryListEntries(anyList(), any(Boolean.class)); - mockMvc.perform(get(URI.create(PATH_API + PATH_QUERY)).with(csrf()).param("skipValidation", "true")) + mockMvc.perform(get(URI.create(PATH_API + PATH_QUERY)).with(csrf()).param("skip-validation", "true")) .andExpect(status().isOk()) .andExpect(jsonPath("$[0].id").value(queryId)) .andExpect(jsonPath("$[0].isValid").doesNotExist()); } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") public void testGetQueryList_SucceedsWithoutDefiningSkipValidation() throws Exception { long queryId = 1; doReturn(List.of(createValidQuery(queryId))).when(queryHandlerService).getQueryListForAuthor(any(String.class), any(Boolean.class)); @@ -329,7 +329,7 @@ public void testGetQueryList_SucceedsWithoutDefiningSkipValidation() throws Exce } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_ADMIN"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_ADMIN"}, username = "test") public void testGetQueryListForUser_SucceedsOnValidUser() throws Exception { long queryId = 1; String userId = "user1"; @@ -342,7 +342,7 @@ public void testGetQueryListForUser_SucceedsOnValidUser() throws Exception { } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_ADMIN"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_ADMIN"}, username = "test") public void testGetQueryListForUser_ReturnsEmptyOnUnknownUser() throws Exception { String userId = "user1"; doReturn(List.of()).when(queryHandlerService).getQueryListForAuthor(any(String.class), any(Boolean.class)); @@ -354,7 +354,7 @@ public void testGetQueryListForUser_ReturnsEmptyOnUnknownUser() throws Exception } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") public void testGetQuery_succeeds() throws Exception { long queryId = 1; var annotatedQuery = createValidAnnotatedStructuredQuery(false); @@ -368,7 +368,7 @@ public void testGetQuery_succeeds() throws Exception { } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") public void testGetQuery_failsOnWrongAuthorWith403() throws Exception { long queryId = 1; var annotatedQuery = createValidAnnotatedStructuredQuery(false); @@ -381,7 +381,7 @@ public void testGetQuery_failsOnWrongAuthorWith403() throws Exception { } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") void testSaveQuery_Succeeds() throws Exception { doReturn("test").when(queryHandlerService).getAuthorId(any(Long.class)); var savedQuery = SavedQuery.builder() @@ -397,7 +397,7 @@ void testSaveQuery_Succeeds() throws Exception { } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") void testSaveQuery_failsWith404OnAuthorForQueryNotFound() throws Exception { doThrow(QueryNotFoundException.class).when(queryHandlerService).getAuthorId(any(Long.class)); @@ -414,7 +414,7 @@ void testSaveQuery_failsWith404OnAuthorForQueryNotFound() throws Exception { } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") void testSaveQuery_failsWith403OnAuthorMismatch() throws Exception { doReturn("SomeOtherUser").when(queryHandlerService).getAuthorId(any(Long.class)); @@ -431,7 +431,7 @@ void testSaveQuery_failsWith403OnAuthorMismatch() throws Exception { } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") void testSaveQuery_failsWith409OnExistingSavedQuery() throws Exception { doReturn("test").when(queryHandlerService).getAuthorId(any(Long.class)); doThrow(DataIntegrityViolationException.class).when(queryHandlerService).saveQuery(any(Long.class), any(String.class), any(SavedQuery.class)); @@ -449,7 +449,7 @@ void testSaveQuery_failsWith409OnExistingSavedQuery() throws Exception { } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") void testSaveQuery_failsWith403OnNoFreeSlots() throws Exception { doReturn("test").when(queryHandlerService).getAuthorId(any(Long.class)); doReturn(maxSavedQueriesPerUser).when(queryHandlerService).getAmountOfSavedQueriesByUser(any(String.class)); @@ -467,7 +467,7 @@ void testSaveQuery_failsWith403OnNoFreeSlots() throws Exception { } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") void testUpdateSavedQuery_Succeeds() throws Exception { doReturn("test").when(queryHandlerService).getAuthorId(any(Long.class)); var savedQuery = SavedQuery.builder() @@ -483,7 +483,7 @@ void testUpdateSavedQuery_Succeeds() throws Exception { } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") void testUpdateSavedQuery_failsWith403OnAuthorMismatch() throws Exception { doReturn("SomeOtherUser").when(queryHandlerService).getAuthorId(any(Long.class)); @@ -500,7 +500,7 @@ void testUpdateSavedQuery_failsWith403OnAuthorMismatch() throws Exception { } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") void testUpdateSavedQuery_failsWith404OnAuthorForQueryNotFound() throws Exception { doThrow(QueryNotFoundException.class).when(queryHandlerService).getAuthorId(any(Long.class)); @@ -517,7 +517,7 @@ void testUpdateSavedQuery_failsWith404OnAuthorForQueryNotFound() throws Exceptio } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") void testDeleteSavedQuery_Succeeds() throws Exception { doReturn("test").when(queryHandlerService).getAuthorId(any(Long.class)); @@ -526,7 +526,7 @@ void testDeleteSavedQuery_Succeeds() throws Exception { } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") void testDeleteSavedQuery_FailsWith403OnWrongAuthor() throws Exception { doReturn("some-other-user").when(queryHandlerService).getAuthorId(any(Long.class)); @@ -535,7 +535,7 @@ void testDeleteSavedQuery_FailsWith403OnWrongAuthor() throws Exception { } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") void testDeleteSavedQuery_FailsWith404IfQueryNotFound() throws Exception { doThrow(QueryNotFoundException.class).when(queryHandlerService).getAuthorId(any(Long.class)); @@ -544,7 +544,7 @@ void testDeleteSavedQuery_FailsWith404IfQueryNotFound() throws Exception { } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") void testDeleteSavedQuery_FailsWith404IfSavedQueryNotFound() throws Exception { doReturn("test").when(queryHandlerService).getAuthorId(any(Long.class)); doThrow(QueryNotFoundException.class).when(queryHandlerService).deleteSavedQuery(any(Long.class)); @@ -555,10 +555,10 @@ void testDeleteSavedQuery_FailsWith404IfSavedQueryNotFound() throws Exception { @ParameterizedTest @EnumSource - @WithMockUser(roles = {"FEASIBILITY_TEST_ADMIN"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_ADMIN"}, username = "test") public void testGetQueryResult_succeeds(QueryHandlerService.ResultDetail resultDetail) throws Exception { var requestUri = PATH_API + PATH_QUERY + "/1"; - doReturn(true).when(authenticationHelper).hasAuthority(any(Authentication.class), eq("FEASIBILITY_TEST_ADMIN")); + doReturn(true).when(authenticationHelper).hasAuthority(any(Authentication.class), eq("DATAPORTAL_TEST_ADMIN")); doReturn("test").when(queryHandlerService).getAuthorId(any(Long.class)); doReturn(createTestQueryResult(resultDetail)).when(queryHandlerService).getQueryResult(any(Long.class), any(QueryHandlerService.ResultDetail.class)); @@ -587,10 +587,10 @@ public void testGetQueryResult_succeeds(QueryHandlerService.ResultDetail resultD } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") public void testGetDetailedObfuscatedQueryResult_returnsIssueWhenBelowThreshold() throws Exception { var requestUri = PATH_API + PATH_QUERY + "/1" + WebSecurityConfig.PATH_DETAILED_OBFUSCATED_RESULT; - doReturn(true).when(authenticationHelper).hasAuthority(any(Authentication.class), eq("FEASIBILITY_TEST_USER")); + doReturn(true).when(authenticationHelper).hasAuthority(any(Authentication.class), eq("DATAPORTAL_TEST_USER")); doReturn("test").when(queryHandlerService).getAuthorId(any(Long.class)); doReturn(createTestDetailedObfuscatedQueryResultWithTooFewResults(thresholdSitesResult)) .when(queryHandlerService).getQueryResult(any(Long.class), any(QueryHandlerService.ResultDetail.class)); @@ -605,17 +605,17 @@ public void testGetDetailedObfuscatedQueryResult_returnsIssueWhenBelowThreshold( } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") public void testGetDetailedObfuscatedResult_failsOnWrongAuthorWith403() throws Exception { doReturn("some-other-user").when(queryHandlerService).getAuthorId(any(Long.class)); - mockMvc.perform(get(URI.create("/api/v3/query/1" + WebSecurityConfig.PATH_DETAILED_OBFUSCATED_RESULT)) + mockMvc.perform(get(URI.create(PATH_API + PATH_QUERY + "/1" + WebSecurityConfig.PATH_DETAILED_OBFUSCATED_RESULT)) .with(csrf())) .andExpect(status().isForbidden()); } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") public void testGetQueryContent_succeeds() throws Exception { doReturn("test").when(queryHandlerService).getAuthorId(any(Long.class)); doReturn(createValidStructuredQuery()).when(queryHandlerService).getQueryContent(any(Long.class)); @@ -627,7 +627,7 @@ public void testGetQueryContent_succeeds() throws Exception { } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") public void testGetQueryContent_failsOnWrongAuthorWith403() throws Exception { doReturn("not-test").when(queryHandlerService).getAuthorId(any(Long.class)); doReturn(createValidStructuredQuery()).when(queryHandlerService).getQueryContent(any(Long.class)); @@ -637,7 +637,7 @@ public void testGetQueryContent_failsOnWrongAuthorWith403() throws Exception { } @Test - @WithMockUser(roles = {"FEASIBILITY_TEST_USER"}, username = "test") + @WithMockUser(roles = {"DATAPORTAL_TEST_USER"}, username = "test") public void testGetDetailedObfuscatedResultRateLimit_succeeds() throws Exception { mockMvc.perform(get(URI.create(PATH_API + PATH_QUERY + "/detailed-obfuscated-result-rate-limit")).with(csrf())) .andExpect(status().isOk()) diff --git a/src/test/java/de/numcodex/feasibility_gui_backend/query/v3/QueryTemplateHandlerRestControllerIT.java b/src/test/java/de/numcodex/feasibility_gui_backend/query/v4/QueryTemplateHandlerRestControllerIT.java similarity index 94% rename from src/test/java/de/numcodex/feasibility_gui_backend/query/v3/QueryTemplateHandlerRestControllerIT.java rename to src/test/java/de/numcodex/feasibility_gui_backend/query/v4/QueryTemplateHandlerRestControllerIT.java index 525c8d19..3bda1bcd 100644 --- a/src/test/java/de/numcodex/feasibility_gui_backend/query/v3/QueryTemplateHandlerRestControllerIT.java +++ b/src/test/java/de/numcodex/feasibility_gui_backend/query/v4/QueryTemplateHandlerRestControllerIT.java @@ -1,4 +1,4 @@ -package de.numcodex.feasibility_gui_backend.query.v3; +package de.numcodex.feasibility_gui_backend.query.v4; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -73,7 +73,7 @@ public class QueryTemplateHandlerRestControllerIT { private AuthenticationHelper authenticationHelper; @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testStoreQueryTemplate_succeedsWith201() throws Exception { long queryId = 1; doReturn(queryId).when(queryHandlerService).storeQueryTemplate(any(QueryTemplate.class), any(String.class)); @@ -87,7 +87,7 @@ public void testStoreQueryTemplate_succeedsWith201() throws Exception { } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testStoreQueryTemplate_failsOnInvalidQueryTemplate() throws Exception { mockMvc.perform(post(URI.create(PATH_API + PATH_QUERY + PATH_TEMPLATE)).with(csrf()) .contentType(APPLICATION_JSON) @@ -96,7 +96,7 @@ public void testStoreQueryTemplate_failsOnInvalidQueryTemplate() throws Exceptio } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testStoreQueryTemplate_failsOnTemplateExceptionWith500() throws Exception { doThrow(QueryTemplateException.class).when(queryHandlerService).storeQueryTemplate(any(QueryTemplate.class), any(String.class)); @@ -107,7 +107,7 @@ public void testStoreQueryTemplate_failsOnTemplateExceptionWith500() throws Exce } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testStoreQueryTemplate_failsOnDuplicateWith409() throws Exception { doThrow(DataIntegrityViolationException.class).when(queryHandlerService).storeQueryTemplate(any(QueryTemplate.class), any(String.class)); @@ -118,7 +118,7 @@ public void testStoreQueryTemplate_failsOnDuplicateWith409() throws Exception { } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testGetQueryTemplate_succeeds() throws Exception { long queryTemplateId = 1; var annotatedQuery = createValidAnnotatedStructuredQuery(false); @@ -133,7 +133,7 @@ public void testGetQueryTemplate_succeeds() throws Exception { } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testGetQueryTemplate_failsOnNotFound() throws Exception { long queryTemplateId = 1; var annotatedQuery = createValidAnnotatedStructuredQuery(false); @@ -147,7 +147,7 @@ public void testGetQueryTemplate_failsOnNotFound() throws Exception { } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testGetQueryTemplate_failsOnJsonError() throws Exception { long queryTemplateId = 1; var annotatedQuery = createValidAnnotatedStructuredQuery(false); @@ -161,20 +161,20 @@ public void testGetQueryTemplate_failsOnJsonError() throws Exception { } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testGetQueryTemplateList_succeeds() throws Exception { int listSize = 5; doReturn(createValidPersistenceQueryTemplateListToGet(listSize)).when(queryHandlerService).getQueryTemplatesForAuthor(any(String.class)); doReturn(createValidApiQueryTemplateToGet(ThreadLocalRandom.current().nextInt())).when(queryHandlerService).convertTemplatePersistenceToApi(any(de.numcodex.feasibility_gui_backend.query.persistence.QueryTemplate.class)); - mockMvc.perform(get(URI.create(PATH_API + PATH_QUERY + PATH_TEMPLATE+ "?skipValidation=true")).with(csrf())) + mockMvc.perform(get(URI.create(PATH_API + PATH_QUERY + PATH_TEMPLATE+ "?skip-validation=true")).with(csrf())) .andExpect(status().isOk()) .andExpect(jsonPath("$.length()").value(listSize)) .andExpect(jsonPath("$.[0].id").exists()); } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testGetQueryTemplateList_emptyListOnJsonErrors() throws Exception { doReturn(createValidPersistenceQueryTemplateListToGet(5)).when(queryHandlerService).getQueryTemplatesForAuthor(any(String.class)); doThrow(JsonProcessingException.class).when(queryHandlerService).convertTemplatePersistenceToApi(any(de.numcodex.feasibility_gui_backend.query.persistence.QueryTemplate.class)); @@ -186,7 +186,7 @@ public void testGetQueryTemplateList_emptyListOnJsonErrors() throws Exception { @ParameterizedTest @ValueSource(booleans = {true, false}) - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testGetQueryTemplateListWithValidation_succeeds(boolean isValid) throws Exception { int listSize = 5; doReturn(createValidPersistenceQueryTemplateListToGet(listSize)).when(queryHandlerService).getQueryTemplatesForAuthor(any(String.class)); @@ -202,7 +202,7 @@ public void testGetQueryTemplateListWithValidation_succeeds(boolean isValid) thr } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testGetQueryTemplateListWithValidation_emptyListOnJsonErrors() throws Exception { int listSize = 5; doReturn(createValidPersistenceQueryTemplateListToGet(listSize)).when(queryHandlerService).getQueryTemplatesForAuthor(any(String.class)); @@ -215,7 +215,7 @@ public void testGetQueryTemplateListWithValidation_emptyListOnJsonErrors() throw } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testUpdateQueryTemplate_succeeds() throws Exception { doNothing().when(queryHandlerService).updateQueryTemplate(any(Long.class), any(QueryTemplate.class), any(String.class)); @@ -226,7 +226,7 @@ public void testUpdateQueryTemplate_succeeds() throws Exception { } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testUpdateQueryTemplate_failsOnInvalidQueryTemplate() throws Exception { mockMvc.perform(put(URI.create(PATH_API + PATH_QUERY + PATH_TEMPLATE + "/1")).with(csrf()) .contentType(APPLICATION_JSON) @@ -235,7 +235,7 @@ public void testUpdateQueryTemplate_failsOnInvalidQueryTemplate() throws Excepti } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testUpdateQueryTemplate_failsOnTemplateExceptionWith404() throws Exception { doThrow(QueryTemplateException.class).when(queryHandlerService).updateQueryTemplate(any(Long.class), any(QueryTemplate.class), any(String.class)); @@ -246,7 +246,7 @@ public void testUpdateQueryTemplate_failsOnTemplateExceptionWith404() throws Exc } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testDeleteQueryTemplate_succeeds() throws Exception { doNothing().when(queryHandlerService).deleteQueryTemplate(any(Long.class), any(String.class)); @@ -255,7 +255,7 @@ public void testDeleteQueryTemplate_succeeds() throws Exception { } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") public void testDeleteQueryTemplate_failsWith404OnNotFound() throws Exception { doThrow(QueryTemplateException.class).when(queryHandlerService).deleteQueryTemplate(any(Long.class), any(String.class)); diff --git a/src/test/java/de/numcodex/feasibility_gui_backend/terminology/TerminologyServiceTest.java b/src/test/java/de/numcodex/feasibility_gui_backend/terminology/TerminologyServiceTest.java index 096b40ab..fbae5287 100644 --- a/src/test/java/de/numcodex/feasibility_gui_backend/terminology/TerminologyServiceTest.java +++ b/src/test/java/de/numcodex/feasibility_gui_backend/terminology/TerminologyServiceTest.java @@ -26,7 +26,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.doReturn; @Tag("terminology") @@ -43,69 +42,24 @@ class TerminologyServiceTest { @Mock private TermCodeRepository termCodeRepository; - @Mock - private ContextualizedTermCodeRepository contextualizedTermCodeRepository; - - @Mock - private MappingRepository mappingRepository; - private final ObjectMapper jsonUtil = new ObjectMapper(); @Mock private Resource terminologySystemsResource; - private TerminologyService createTerminologyService(String uiProfilePath) throws IOException { - return new TerminologyService(uiProfilePath,"src/test/resources/ontology/terminology_systems.json", uiProfileRepository, termCodeRepository, contextualizedTermCodeRepository, mappingRepository, jsonUtil); + private TerminologyService createTerminologyService() throws IOException { + return new TerminologyService("src/test/resources/ontology/terminology_systems.json", uiProfileRepository, termCodeRepository, jsonUtil); } @BeforeEach void setUp() { - Mockito.reset(uiProfileRepository, termCodeRepository, contextualizedTermCodeRepository, mappingRepository); - } - - @Test - void testInitFailsOnNonexistingFolderWithNullpointerException() { - assertThrows(NullPointerException.class, () -> createTerminologyService("does/not/exist")); - } - - @Test - void getUiProfile_succeedsOnKnownHash() throws IOException { - var terminologyService = createTerminologyService("src/test/resources/ontology/ui_profiles"); - doReturn(Optional.of(createUiProfile())).when(uiProfileRepository).findByContextualizedTermcodeHash(any(String.class)); - - var uiProfileResult = assertDoesNotThrow(() -> terminologyService.getUiProfile(VALID_NODE_ID_CAT1.toString())); - assertFalse(uiProfileResult.isBlank()); - } - - @Test - void getUiProfile_throwsOnUnknownHash() throws IOException { - var terminologyService = createTerminologyService("src/test/resources/ontology/ui_profiles"); - doReturn(Optional.empty()).when(uiProfileRepository).findByContextualizedTermcodeHash(any(String.class)); - - assertThrows(UiProfileNotFoundException.class, () -> terminologyService.getUiProfile(INVALID_NODE_ID.toString())); - } - - @Test - void getMapping_succeedsOnKnownHash() throws IOException { - var terminologyService = createTerminologyService("src/test/resources/ontology/ui_profiles"); - doReturn(Optional.of(createMapping())).when(mappingRepository).findByContextualizedTermcodeHash(any(String.class)); - - var mappingResult = assertDoesNotThrow(() -> terminologyService.getMapping(VALID_NODE_ID_CAT1.toString())); - assertFalse(mappingResult.isBlank()); - } - - @Test - void getMapping_throwsOnUnknownHash() throws IOException { - var terminologyService = createTerminologyService("src/test/resources/ontology/ui_profiles"); - doReturn(Optional.empty()).when(mappingRepository).findByContextualizedTermcodeHash(any(String.class)); - - assertThrows(MappingNotFoundException.class, () -> terminologyService.getMapping(INVALID_NODE_ID.toString())); + Mockito.reset(uiProfileRepository, termCodeRepository); } @ParameterizedTest @ValueSource(strings = {"true", "false"}) void isExistingTermCode(boolean doesExist) throws IOException { - var terminologyService = createTerminologyService("src/test/resources/ontology/ui_profiles"); + var terminologyService = createTerminologyService(); doReturn(doesExist).when(termCodeRepository).existsTermCode(any(String.class), any(String.class)); doReturn(doesExist).when(termCodeRepository).existsTermCode(any(String.class), any(String.class), any(String.class)); @@ -136,29 +90,9 @@ void testMin_emptyArray() { assertEquals(expected, result); } - @Test - void getIntersection_succeeds() throws IOException { - var terminologyService = createTerminologyService("src/test/resources/ontology/ui_profiles"); - doReturn(List.of()).when(contextualizedTermCodeRepository).filterByCriteriaSetUrl(any(String.class), anyList()); - - var intersection = terminologyService.getIntersection("http://foo.bar", List.of(UUID.randomUUID().toString())); - - assertTrue(intersection.isEmpty()); - } - - @Test - void getIntersection_succeedsWithEmptyList() throws IOException { - var terminologyService = createTerminologyService("src/test/resources/ontology/ui_profiles"); - doReturn(List.of()).when(contextualizedTermCodeRepository).filterByCriteriaSetUrl(any(String.class), anyList()); - - var intersection = terminologyService.getIntersection("http://foo.bar", List.of()); - - assertTrue(intersection.isEmpty()); - } - @Test void getCriteriaProfileData_emptyIdsResultInEmptyList() throws IOException { - var terminologyService = createTerminologyService("src/test/resources/ontology/ui_profiles"); + var terminologyService = createTerminologyService(); var result = assertDoesNotThrow(() -> terminologyService.getCriteriaProfileData(List.of())); assertTrue(result.isEmpty()); @@ -176,7 +110,7 @@ void getCriteriaProfileData_emptyIdsResultInEmptyList() throws IOException { "false, false, false" }) void getCriteriaProfileData(String noUiProfile, String noContext, String noTermcodes) throws IOException { - var terminologyService = createTerminologyService("src/test/resources/ontology/ui_profiles"); + var terminologyService = createTerminologyService(); List ids = List.of("123", "456", "789"); boolean excludeUiProfile = Boolean.parseBoolean(noUiProfile); boolean excludeContext = Boolean.parseBoolean(noContext); @@ -213,7 +147,7 @@ void getCriteriaProfileData(String noUiProfile, String noContext, String noTermc @Test void getTerminologySystems_succeeds() throws IOException, NoSuchFieldException, IllegalAccessException { - var terminologyService = createTerminologyService("src/test/resources/ontology/ui_profiles"); + var terminologyService = createTerminologyService(); var terminologySystems = assertDoesNotThrow(terminologyService::getTerminologySystems); @@ -236,22 +170,6 @@ private UiProfile createUiProfile() throws JsonProcessingException { return uiProfile; } - private Mapping createMapping() { - var mapping = new Mapping(); - mapping.setId(1); - mapping.setName("example"); - mapping.setType("mapping-type"); - mapping.setContent(""" - { - "name": "ExampleMapping", - "content": "tbd" - } - """ - ); - - return mapping; - } - private TermCode createTermCode() { TermCode termCode = new TermCode(); termCode.setId(1); diff --git a/src/test/java/de/numcodex/feasibility_gui_backend/terminology/v3/TerminologyEsRestControllerIT.java b/src/test/java/de/numcodex/feasibility_gui_backend/terminology/v3/TerminologyEsRestControllerIT.java deleted file mode 100644 index 2d91ab14..00000000 --- a/src/test/java/de/numcodex/feasibility_gui_backend/terminology/v3/TerminologyEsRestControllerIT.java +++ /dev/null @@ -1,213 +0,0 @@ -package de.numcodex.feasibility_gui_backend.terminology.v3; - -import com.fasterxml.jackson.databind.ObjectMapper; -import de.numcodex.feasibility_gui_backend.query.ratelimiting.RateLimitingInterceptor; -import de.numcodex.feasibility_gui_backend.query.ratelimiting.RateLimitingServiceSpringConfig; -import de.numcodex.feasibility_gui_backend.terminology.api.EsSearchResult; -import de.numcodex.feasibility_gui_backend.terminology.api.EsSearchResultEntry; -import de.numcodex.feasibility_gui_backend.terminology.es.TerminologyEsService; -import de.numcodex.feasibility_gui_backend.terminology.es.model.*; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; - -import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.doReturn; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -@Tag("terminology") -@Tag("elasticsearch") -@ExtendWith(SpringExtension.class) -@Import(RateLimitingServiceSpringConfig.class) -@WebMvcTest( - controllers = TerminologyEsRestController.class -) -class TerminologyEsRestControllerIT { - - @Autowired - private MockMvc mockMvc; - - @MockBean - private TerminologyEsService terminologyEsService; - - @MockBean - private RateLimitingInterceptor rateLimitingInterceptor; - - @Autowired - private ObjectMapper jsonUtil; - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetFilters_succeeds() throws Exception { - List filterList = List.of("context", "kdsModule", "terminology"); - List termFilterList = createTermFilterList(filterList.toArray(new String[0])); - doReturn(termFilterList).when(terminologyEsService).getAvailableFilters(); - - mockMvc.perform(get(URI.create("/api/v3/terminology/search/filter")).with(csrf())) - .andExpect(status().isOk()) - .andExpect(content().json(jsonUtil.writeValueAsString(termFilterList))); - } - - @Test - public void testGetFilters_failsOnUnauthorized() throws Exception { - mockMvc.perform(get(URI.create("/api/v3/terminology/search/filter")).with(csrf())) - .andExpect(status().isUnauthorized()); - } - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testSearchOntologyItemsCriteriaQuery_succeeds() throws Exception { - var totalHits = 1; - var dummyEsSearchResult = createDummyEsSearchResult(totalHits); - doReturn(dummyEsSearchResult).when(terminologyEsService).performOntologySearchWithPaging(any(String.class), isNull(), isNull(), isNull(), isNull(), anyBoolean(), anyInt(), anyInt()); - - mockMvc.perform(get(URI.create("/api/v3/terminology/entry/search")).param("searchterm", "some-context").with(csrf())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.totalHits").value(dummyEsSearchResult.totalHits())) - .andExpect(jsonPath("$.results[0].id").value(dummyEsSearchResult.results().get(0).id())) - .andExpect(jsonPath("$.results[0].name").value(dummyEsSearchResult.results().get(0).name())) - .andExpect(jsonPath("$.results[0].terminology").value(dummyEsSearchResult.results().get(0).terminology())) - .andExpect(jsonPath("$.results[0].selectable").value(dummyEsSearchResult.results().get(0).selectable())) - .andExpect(jsonPath("$.results[0].kdsModule").value(dummyEsSearchResult.results().get(0).kdsModule())) - .andExpect(jsonPath("$.results[0].availability").value(dummyEsSearchResult.results().get(0).availability())) - .andExpect(jsonPath("$.results[0].context").value(dummyEsSearchResult.results().get(0).context())); - - } - - @Test - public void testSearchOntologyItemsCriteriaQuery_failsOnUnauthorized() throws Exception { - doReturn(createDummyEsSearchResult(1)).when(terminologyEsService).performOntologySearchWithPaging(any(String.class), anyList(), anyList(), anyList(), anyList(), any(Boolean.class), any(Integer.class), any(Integer.class)); - - mockMvc.perform(get(URI.create("/api/v3/terminology/entry/search")).param("searchterm", "some-context").with(csrf())) - .andExpect(status().isUnauthorized()); - } - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetOntologyItemRelationsByHash_succeeds() throws Exception { - var dummyOntologyItemRelations = createDummyOntologyItemRelations(); - doReturn(dummyOntologyItemRelations).when(terminologyEsService).getOntologyItemRelationsByHash(any(String.class)); - - mockMvc.perform(get(URI.create("/api/v3/terminology/entry/abc/relations")).with(csrf())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.translations[0].lang").value(dummyOntologyItemRelations.translations().stream().toList().get(0).lang())) - .andExpect(jsonPath("$.translations[0].value").value(dummyOntologyItemRelations.translations().stream().toList().get(0).value())) - .andExpect(jsonPath("$.children[0].contextualizedTermcodeHash").value(dummyOntologyItemRelations.children().stream().toList().get(0).contextualizedTermcodeHash())) - .andExpect(jsonPath("$.children[0].name").value(dummyOntologyItemRelations.children().stream().toList().get(0).name())) - .andExpect(jsonPath("$.parents[0].contextualizedTermcodeHash").value(dummyOntologyItemRelations.parents().stream().toList().get(0).contextualizedTermcodeHash())) - .andExpect(jsonPath("$.parents[0].name").value(dummyOntologyItemRelations.parents().stream().toList().get(0).name())) - .andExpect(jsonPath("$.relatedTerms[0].contextualizedTermcodeHash").value(dummyOntologyItemRelations.relatedTerms().stream().toList().get(0).contextualizedTermcodeHash())) - .andExpect(jsonPath("$.relatedTerms[0].name").value(dummyOntologyItemRelations.relatedTerms().stream().toList().get(0).name())); - } - - @Test - public void testGetOntologyItemRelationsByHash_failsOnUnauthorized() throws Exception { - var dummyOntologyItemRelations = createDummyOntologyItemRelations(); - doReturn(dummyOntologyItemRelations).when(terminologyEsService).getOntologyItemRelationsByHash(any(String.class)); - - mockMvc.perform(get(URI.create("/api/v3/terminology/entry/abc/relations")).with(csrf())) - .andExpect(status().isUnauthorized()); - } - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetOntologyItemByHash_succeeds() throws Exception { - var dummySearchResultEntry = createDummyEsSearchResultEntry(); - doReturn(dummySearchResultEntry).when(terminologyEsService).getSearchResultEntryByHash(any(String.class)); - - mockMvc.perform(get(URI.create("/api/v3/terminology/entry/abc")).with(csrf())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").value(dummySearchResultEntry.id())) - .andExpect(jsonPath("$.name").value(dummySearchResultEntry.name())) - .andExpect(jsonPath("$.availability").value(dummySearchResultEntry.availability())) - .andExpect(jsonPath("$.context").value(dummySearchResultEntry.context())) - .andExpect(jsonPath("$.terminology").value(dummySearchResultEntry.terminology())) - .andExpect(jsonPath("$.termcode").value(dummySearchResultEntry.termcode())) - .andExpect(jsonPath("$.kdsModule").value(dummySearchResultEntry.kdsModule())) - .andExpect(jsonPath("$.selectable").value(dummySearchResultEntry.selectable())); - } - - @Test - public void testGetOntologyItemByHash_failsOnUnauthorized() throws Exception { - var dummySearchResultEntry = createDummyEsSearchResultEntry(); - doReturn(dummySearchResultEntry).when(terminologyEsService).getSearchResultEntryByHash(any(String.class)); - - mockMvc.perform(get(URI.create("/api/v3/terminology/entry/abc")).with(csrf())) - .andExpect(status().isUnauthorized()); - } - - private EsSearchResult createDummyEsSearchResult(int totalHits) { - return EsSearchResult.builder() - .totalHits(totalHits) - .results(List.of(createDummyEsSearchResultEntry())) - .build(); - } - - private EsSearchResultEntry createDummyEsSearchResultEntry() { - return EsSearchResultEntry.builder() - .terminology("some-terminology") - .availability(100) - .context("some-context") - .id("abc-123") - .kdsModule("some-module") - .name("some-name") - .selectable(true) - .build(); - } - - private OntologyItemRelationsDocument createDummyOntologyItemRelations() { - return OntologyItemRelationsDocument.builder() - .relatedTerms(List.of(createDummyRelative())) - .translations(List.of(createDummyTranslation())) - .parents(List.of(createDummyRelative())) - .children(List.of(createDummyRelative())) - .build(); - } - - private Translation createDummyTranslation() { - return Translation.builder() - .lang("de") - .value("Lorem Ipsum") - .build(); - } - - private Relative createDummyRelative() { - return Relative.builder() - .contextualizedTermcodeHash(UUID.randomUUID().toString()) - .name("some-random-name") - .build(); - } - - private List createTermFilterList(String[] values) { - var termFilters = new ArrayList(); - for (String term : values) { - termFilters.add( - TermFilter.builder() - .name(term) - .type("selectable-concept") - .values(List.of(TermFilterValue.builder().label("baz").count(values.length).build())) - .build() - ); - } - termFilters.add(TermFilter.builder() - .type("boolean") - .name("availability") - .values(List.of()) - .build()); - return termFilters; - } -} diff --git a/src/test/java/de/numcodex/feasibility_gui_backend/terminology/v3/TerminologyRestControllerIT.java b/src/test/java/de/numcodex/feasibility_gui_backend/terminology/v3/TerminologyRestControllerIT.java deleted file mode 100644 index dd1c4213..00000000 --- a/src/test/java/de/numcodex/feasibility_gui_backend/terminology/v3/TerminologyRestControllerIT.java +++ /dev/null @@ -1,361 +0,0 @@ -package de.numcodex.feasibility_gui_backend.terminology.v3; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import de.numcodex.feasibility_gui_backend.common.api.Comparator; -import de.numcodex.feasibility_gui_backend.common.api.TermCode; -import de.numcodex.feasibility_gui_backend.query.ratelimiting.RateLimitingInterceptor; -import de.numcodex.feasibility_gui_backend.query.ratelimiting.RateLimitingServiceSpringConfig; -import de.numcodex.feasibility_gui_backend.terminology.MappingNotFoundException; -import de.numcodex.feasibility_gui_backend.terminology.NodeNotFoundException; -import de.numcodex.feasibility_gui_backend.terminology.TerminologyService; -import de.numcodex.feasibility_gui_backend.terminology.UiProfileNotFoundException; -import de.numcodex.feasibility_gui_backend.terminology.api.*; -import de.numcodex.feasibility_gui_backend.terminology.validation.StructuredQueryValidation; -import org.hamcrest.Matchers; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.mockito.Mockito; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; - -import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import static de.numcodex.feasibility_gui_backend.config.WebSecurityConfig.PATH_API; -import static de.numcodex.feasibility_gui_backend.config.WebSecurityConfig.PATH_TERMINOLOGY; -import static org.hamcrest.Matchers.hasSize; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -@Tag("terminology") -@ExtendWith(SpringExtension.class) -@Import(RateLimitingServiceSpringConfig.class) -@WebMvcTest( - controllers = TerminologyRestController.class -) -public class TerminologyRestControllerIT { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private ObjectMapper jsonUtil; - - @MockBean - private StructuredQueryValidation structuredQueryValidation; - - @MockBean - private TerminologyService terminologyService; - - @MockBean - private RateLimitingInterceptor rateLimitingInterceptor; - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetCriteriaProfileData_succeedsWith200() throws Exception { - var id = UUID.randomUUID(); - var criteriaProfileDataList = createCriteriaProfileDataList(List.of(id)); - doReturn(criteriaProfileDataList).when(terminologyService).getCriteriaProfileData(anyList()); - - mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/criteria-profile-data")).param("ids", id.toString()).with(csrf())) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$.*", hasSize(1))) - .andExpect(jsonPath("$.[0].id").value(id.toString())); - } - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetCriteriaProfileData_succeedsWith200OnEmptyList() throws Exception { - doReturn(List.of()).when(terminologyService).getCriteriaProfileData(anyList()); - - mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/criteria-profile-data")).param("ids", "123").with(csrf())) - .andExpect(status().isOk()) - .andExpect(content().contentType(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$.*", hasSize(0))); - } - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetEntry_succeedsWith200() throws Exception { - var id = UUID.randomUUID(); - var terminologyEntry = createTerminologyEntry(id); - doReturn(terminologyEntry).when(terminologyService).getEntry(any(UUID.class)); - - mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/entries/" + id)).with(csrf())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id").value(id.toString())) - .andExpect(jsonPath("$.context.code").value(terminologyEntry.getContext().code())) - .andExpect(jsonPath("$.context.system").value(terminologyEntry.getContext().system())) - .andExpect(jsonPath("$.context.display").value(terminologyEntry.getContext().display())) - .andExpect(jsonPath("$.termCodes.[0].code").value(terminologyEntry.getTermCodes().get(0).code())) - .andExpect(jsonPath("$.termCodes.[0].system").value(terminologyEntry.getTermCodes().get(0).system())) - .andExpect(jsonPath("$.termCodes.[0].display").value(terminologyEntry.getTermCodes().get(0).display())); - } - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetEntry_failsWith404OnNotFound() throws Exception { - Mockito.doThrow(NodeNotFoundException.class).when(terminologyService).getEntry(any(UUID.class)); - - mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/entries/" + UUID.randomUUID())).with(csrf())) - .andExpect(status().isNotFound()); - } - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetCategories_succeedsWith200() throws Exception { - List categoryEntries = createCategoryEntries(); - doReturn(categoryEntries).when(terminologyService).getCategories(); - - mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/categories")).with(csrf())) - .andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.length()").value(categoryEntries.size())) - .andExpect(jsonPath("$.[*].catId").exists()) - .andExpect(jsonPath("$.[*].display").exists()) - .andExpect(jsonPath("$[*].display", Matchers.everyItem(Matchers.containsString("category-")))); - } - - @ParameterizedTest - @ValueSource(strings = {"true", "false"}) - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetSelectableEntries_succeedsWith200(boolean includeCategory) throws Exception { - var id = UUID.randomUUID(); - var terminologyEntries = List.of(createTerminologyEntry(id)); - MockHttpServletRequestBuilder requestBuilder; - doReturn(terminologyEntries).when(terminologyService).getSelectableEntries(any(String.class), any(UUID.class)); - doReturn(terminologyEntries).when(terminologyService).getSelectableEntries(any(String.class), isNull()); - - if (includeCategory) { - requestBuilder = get(URI.create(PATH_API + PATH_TERMINOLOGY + "/entries")) - .param("query", "GESCH") - .param("categoryId", UUID.randomUUID().toString()) - .with(csrf()); - - } else { - requestBuilder = get(URI.create(PATH_API + PATH_TERMINOLOGY + "/entries")) - .param("query", "GESCH") - .with(csrf()); - - } - mockMvc.perform(requestBuilder) - .andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.[0].id").value(id.toString())) - .andExpect(jsonPath("$.[0].context.code").value(terminologyEntries.get(0).getContext().code())) - .andExpect(jsonPath("$.[0].context.system").value(terminologyEntries.get(0).getContext().system())) - .andExpect(jsonPath("$.[0].context.display").value(terminologyEntries.get(0).getContext().display())) - .andExpect(jsonPath("$.[0].termCodes.[0].code").value(terminologyEntries.get(0).getTermCodes().get(0).code())) - .andExpect(jsonPath("$.[0].termCodes.[0].system").value(terminologyEntries.get(0).getTermCodes().get(0).system())) - .andExpect(jsonPath("$.[0].termCodes.[0].display").value(terminologyEntries.get(0).getTermCodes().get(0).display())); - } - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetSelectableEntries_succeedsWith200ButEmptyOnNoMatch() throws Exception { - doReturn(List.of()).when(terminologyService).getSelectableEntries(any(String.class), any(UUID.class)); - - mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/entries")) - .param("query", "GESCH") - .with(csrf())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.length()").value(0)); - } - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetSelectableEntries_failsWith400OnMissingQueryParameter() throws Exception { - doReturn(List.of()).when(terminologyService).getSelectableEntries(any(String.class), any(UUID.class)); - - mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/entries")) - .with(csrf())) - .andExpect(status().isBadRequest()); - } - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetUiProfile_succeedsWith200() throws Exception { - MockHttpServletRequestBuilder requestBuilder; - doReturn(createUiProfileString()).when(terminologyService).getUiProfile(any(String.class)); - - requestBuilder = get(URI.create(PATH_API + PATH_TERMINOLOGY + "/5e6679ac-2b20-48ad-8459-f102c8944a06/ui_profile")) - .with(csrf()); - - mockMvc.perform(requestBuilder) - .andExpect(status().isOk()) - .andExpect(content().string(createUiProfileString())); - } - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetUiProfile_failsWith404OnNotFound() throws Exception { - MockHttpServletRequestBuilder requestBuilder; - doThrow(UiProfileNotFoundException.class).when(terminologyService).getUiProfile(any(String.class)); - - requestBuilder = get(URI.create(PATH_API + PATH_TERMINOLOGY + "/5e6679ac-2b20-48ad-8459-f102c8944a06/ui_profile")) - .with(csrf()); - - mockMvc.perform(requestBuilder) - .andExpect(status().isNotFound()); - } - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetMapping_succeedsWith200() throws Exception { - MockHttpServletRequestBuilder requestBuilder; - doReturn(createUiProfileString()).when(terminologyService).getMapping(any(String.class)); - - requestBuilder = get(URI.create(PATH_API + PATH_TERMINOLOGY + "/5e6679ac-2b20-48ad-8459-f102c8944a06/mapping")) - .with(csrf()); - - mockMvc.perform(requestBuilder) - .andExpect(status().isOk()) - .andExpect(content().string(createUiProfileString())); - } - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetMapping_failsWith404OnNotFound() throws Exception { - MockHttpServletRequestBuilder requestBuilder; - doThrow(MappingNotFoundException.class).when(terminologyService).getMapping(any(String.class)); - - requestBuilder = get(URI.create(PATH_API + PATH_TERMINOLOGY + "/5e6679ac-2b20-48ad-8459-f102c8944a06/mapping")) - .with(csrf()); - - mockMvc.perform(requestBuilder) - .andExpect(status().isNotFound()); - } - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetIntersection_succeedsWith200() throws Exception { - MockHttpServletRequestBuilder requestBuilder; - var randomUuidList = List.of(UUID.randomUUID().toString(), UUID.randomUUID().toString(), UUID.randomUUID().toString()); - var randomUuidListMinusOne = randomUuidList.subList(0, randomUuidList.size() - 2); - doReturn(randomUuidListMinusOne).when(terminologyService).getIntersection(any(String.class), anyList()); - - requestBuilder = post(URI.create(PATH_API + PATH_TERMINOLOGY + "/criteria-set/intersect")) - .param("criteriaSetUrl", "http://foo.bar") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .content(jsonUtil.writeValueAsString(randomUuidList)) - .with(csrf()); - - mockMvc.perform(requestBuilder) - .andExpect(status().isOk()) - .andExpect(content().json(jsonUtil.writeValueAsString(randomUuidListMinusOne))); - } - - @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") - public void testGetTerminologySystems_succeedsWith200() throws Exception { - MockHttpServletRequestBuilder requestBuilder; - doReturn(List.of(TerminologySystemEntry.builder().url("http://foo.bar").name("Foobar").build())).when(terminologyService).getTerminologySystems(); - - requestBuilder = get(URI.create(PATH_API + PATH_TERMINOLOGY + "/systems")) - .with(csrf()); - - mockMvc.perform(requestBuilder) - .andExpect(status().isOk()) - .andExpect(jsonPath("$[0].url").value("http://foo.bar")) - .andExpect(jsonPath("$[0].name").value("Foobar")); - } - - private List createCategoryEntries() { - return List.of(new CategoryEntry(UUID.randomUUID(), "category-1"), - new CategoryEntry(UUID.randomUUID(), "category-2"), - new CategoryEntry(UUID.randomUUID(), "category-3"), - new CategoryEntry(UUID.randomUUID(), "category-4"), - new CategoryEntry(UUID.randomUUID(), "category-5")); - } - - private TerminologyEntry createTerminologyEntry(UUID id) { - TerminologyEntry terminologyEntry = new TerminologyEntry(); - terminologyEntry.setId(id); - terminologyEntry.setContext(createTermCode()); - terminologyEntry.setTermCodes(List.of(createTermCode())); - terminologyEntry.setChildren(List.of()); - terminologyEntry.setLeaf(true); - terminologyEntry.setSelectable(true); - terminologyEntry.setDisplay("TerminologyEntry"); - terminologyEntry.setRoot(false); - return terminologyEntry; - } - - private TermCode createTermCode() { - return TermCode.builder() - .code("LL2191-6") - .system("http://loinc.org") - .display("Geschlecht") - .version("1.0.0") - .build(); - } - - private String createUiProfileString() throws JsonProcessingException { - var uiProfile = UiProfile.builder() - .name("Diagnose") - .timeRestrictionAllowed(true) - .build(); - return jsonUtil.writeValueAsString(uiProfile); - } - - private UiProfile createUiProfile() { - return UiProfile.builder() - .name("test-ui-profile") - .attributeDefinitions(List.of(createAttributeDefinition())) - .valueDefinition(createAttributeDefinition()) - .timeRestrictionAllowed(true) - .build(); - } - - private AttributeDefinition createAttributeDefinition() { - return AttributeDefinition.builder() - .min(1.0) - .max(99.9) - .allowedUnits(List.of(createTermCode())) - .attributeCode(createTermCode()) - .type(ValueDefinitonType.CONCEPT) - .optional(false) - .referencedCriteriaSet("http://my.reference.criteria/set") - .referencedValueSet("http://my.reference.value/set") - .comparator(Comparator.EQUAL) - .precision(1.0) - .selectableConcepts(List.of(createTermCode())) - .build(); - } - - private List createCriteriaProfileDataList(List ids) { - List criteriaProfileDataList = new ArrayList<>(); - for (UUID uuid: ids) { - criteriaProfileDataList.add( - CriteriaProfileData.builder() - .id(uuid.toString()) - .context(createTermCode()) - .termCodes(List.of(createTermCode())) - .uiProfile(createUiProfile()) - .build() - ); - } - return criteriaProfileDataList; - } -} diff --git a/src/test/java/de/numcodex/feasibility_gui_backend/terminology/v3/CodeableConceptEsRestControllerIT.java b/src/test/java/de/numcodex/feasibility_gui_backend/terminology/v4/CodeableConceptRestControllerIT.java similarity index 94% rename from src/test/java/de/numcodex/feasibility_gui_backend/terminology/v3/CodeableConceptEsRestControllerIT.java rename to src/test/java/de/numcodex/feasibility_gui_backend/terminology/v4/CodeableConceptRestControllerIT.java index fdada0ca..9811a359 100644 --- a/src/test/java/de/numcodex/feasibility_gui_backend/terminology/v3/CodeableConceptEsRestControllerIT.java +++ b/src/test/java/de/numcodex/feasibility_gui_backend/terminology/v4/CodeableConceptRestControllerIT.java @@ -1,4 +1,4 @@ -package de.numcodex.feasibility_gui_backend.terminology.v3; +package de.numcodex.feasibility_gui_backend.terminology.v4; import com.fasterxml.jackson.databind.ObjectMapper; import de.numcodex.feasibility_gui_backend.common.api.TermCode; @@ -34,9 +34,9 @@ @ExtendWith(SpringExtension.class) @Import(RateLimitingServiceSpringConfig.class) @WebMvcTest( - controllers = CodeableConceptEsRestController.class + controllers = CodeableConceptRestController.class ) -class CodeableConceptEsRestControllerIT { +class CodeableConceptRestControllerIT { @Autowired private MockMvc mockMvc; @@ -51,7 +51,7 @@ class CodeableConceptEsRestControllerIT { private RateLimitingInterceptor rateLimitingInterceptor; @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") void testSearchOntologyItemsCriteriaQuery_succeedsWith200() throws Exception { CcSearchResult dummyCcSearchResult = createDummyCcSearchResult(); doReturn(dummyCcSearchResult).when(codeableConceptService).performCodeableConceptSearchWithRepoAndPaging(any(String.class), isNull(), anyInt(), anyInt()); @@ -70,7 +70,7 @@ void testSearchOntologyItemsCriteriaQuery_succeedsWith200() throws Exception { } @Test - @WithMockUser(roles = "FEASIBILITY_TEST_USER") + @WithMockUser(roles = "DATAPORTAL_TEST_USER") void testGetCodeableConceptByCode_succeedsWith200() throws Exception { TermCode dummyTermcode = createDummyTermcode(); doReturn(dummyTermcode).when(codeableConceptService).getSearchResultEntryByCode(any(String.class)); diff --git a/src/test/java/de/numcodex/feasibility_gui_backend/terminology/v4/TerminologyRestControllerIT.java b/src/test/java/de/numcodex/feasibility_gui_backend/terminology/v4/TerminologyRestControllerIT.java new file mode 100644 index 00000000..8717c808 --- /dev/null +++ b/src/test/java/de/numcodex/feasibility_gui_backend/terminology/v4/TerminologyRestControllerIT.java @@ -0,0 +1,315 @@ +package de.numcodex.feasibility_gui_backend.terminology.v4; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.numcodex.feasibility_gui_backend.common.api.Comparator; +import de.numcodex.feasibility_gui_backend.common.api.TermCode; +import de.numcodex.feasibility_gui_backend.query.ratelimiting.RateLimitingInterceptor; +import de.numcodex.feasibility_gui_backend.query.ratelimiting.RateLimitingServiceSpringConfig; +import de.numcodex.feasibility_gui_backend.terminology.TerminologyService; +import de.numcodex.feasibility_gui_backend.terminology.api.*; +import de.numcodex.feasibility_gui_backend.terminology.es.TerminologyEsService; +import de.numcodex.feasibility_gui_backend.terminology.es.model.*; +import de.numcodex.feasibility_gui_backend.terminology.validation.StructuredQueryValidation; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static de.numcodex.feasibility_gui_backend.config.WebSecurityConfig.PATH_API; +import static de.numcodex.feasibility_gui_backend.config.WebSecurityConfig.PATH_TERMINOLOGY; +import static org.hamcrest.Matchers.hasSize; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.doReturn; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@Tag("terminology") +@ExtendWith(SpringExtension.class) +@Import(RateLimitingServiceSpringConfig.class) +@WebMvcTest( + controllers = TerminologyRestController.class +) +public class TerminologyRestControllerIT { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper jsonUtil; + + @MockBean + private StructuredQueryValidation structuredQueryValidation; + + @MockBean + private TerminologyService terminologyService; + + @MockBean + private TerminologyEsService terminologyEsService; + + @MockBean + private RateLimitingInterceptor rateLimitingInterceptor; + + @Test + @WithMockUser(roles = "DATAPORTAL_TEST_USER") + public void testGetCriteriaProfileData_succeedsWith200() throws Exception { + var id = UUID.randomUUID(); + var criteriaProfileDataList = createCriteriaProfileDataList(List.of(id)); + doReturn(criteriaProfileDataList).when(terminologyService).getCriteriaProfileData(anyList()); + + mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/criteria-profile-data")).param("ids", id.toString()).with(csrf())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.*", hasSize(1))) + .andExpect(jsonPath("$.[0].id").value(id.toString())); + } + + @Test + @WithMockUser(roles = "DATAPORTAL_TEST_USER") + public void testGetCriteriaProfileData_succeedsWith200OnEmptyList() throws Exception { + doReturn(List.of()).when(terminologyService).getCriteriaProfileData(anyList()); + + mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/criteria-profile-data")).param("ids", "123").with(csrf())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.*", hasSize(0))); + } + + @Test + @WithMockUser(roles = "DATAPORTAL_TEST_USER") + public void testGetTerminologySystems_succeedsWith200() throws Exception { + MockHttpServletRequestBuilder requestBuilder; + doReturn(List.of(TerminologySystemEntry.builder().url("http://foo.bar").name("Foobar").build())).when(terminologyService).getTerminologySystems(); + + requestBuilder = get(URI.create(PATH_API + PATH_TERMINOLOGY + "/systems")) + .with(csrf()); + + mockMvc.perform(requestBuilder) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].url").value("http://foo.bar")) + .andExpect(jsonPath("$[0].name").value("Foobar")); + } + + @Test + @WithMockUser(roles = "DATAPORTAL_TEST_USER") + public void testGetFilters_succeeds() throws Exception { + List filterList = List.of("context", "kdsModule", "terminology"); + List termFilterList = createTermFilterList(filterList.toArray(new String[0])); + doReturn(termFilterList).when(terminologyEsService).getAvailableFilters(); + + mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/search/filter")).with(csrf())) + .andExpect(status().isOk()) + .andExpect(content().json(jsonUtil.writeValueAsString(termFilterList))); + } + + @Test + public void testGetFilters_failsOnUnauthorized() throws Exception { + mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/search/filter")).with(csrf())) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser(roles = "DATAPORTAL_TEST_USER") + public void testSearchOntologyItemsCriteriaQuery_succeeds() throws Exception { + var totalHits = 1; + var dummyEsSearchResult = createDummyEsSearchResult(totalHits); + doReturn(dummyEsSearchResult).when(terminologyEsService).performOntologySearchWithPaging(any(String.class), isNull(), isNull(), isNull(), isNull(), anyBoolean(), anyInt(), anyInt()); + + mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/entry/search")).param("searchterm", "some-context").with(csrf())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.totalHits").value(dummyEsSearchResult.totalHits())) + .andExpect(jsonPath("$.results[0].id").value(dummyEsSearchResult.results().get(0).id())) + .andExpect(jsonPath("$.results[0].name").value(dummyEsSearchResult.results().get(0).name())) + .andExpect(jsonPath("$.results[0].terminology").value(dummyEsSearchResult.results().get(0).terminology())) + .andExpect(jsonPath("$.results[0].selectable").value(dummyEsSearchResult.results().get(0).selectable())) + .andExpect(jsonPath("$.results[0].kdsModule").value(dummyEsSearchResult.results().get(0).kdsModule())) + .andExpect(jsonPath("$.results[0].availability").value(dummyEsSearchResult.results().get(0).availability())) + .andExpect(jsonPath("$.results[0].context").value(dummyEsSearchResult.results().get(0).context())); + + } + + @Test + public void testSearchOntologyItemsCriteriaQuery_failsOnUnauthorized() throws Exception { + doReturn(createDummyEsSearchResult(1)).when(terminologyEsService).performOntologySearchWithPaging(any(String.class), anyList(), anyList(), anyList(), anyList(), any(Boolean.class), any(Integer.class), any(Integer.class)); + + mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/entry/search")).param("searchterm", "some-context").with(csrf())) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser(roles = "DATAPORTAL_TEST_USER") + public void testGetOntologyItemRelationsByHash_succeeds() throws Exception { + var dummyOntologyItemRelations = createDummyOntologyItemRelations(); + doReturn(dummyOntologyItemRelations).when(terminologyEsService).getOntologyItemRelationsByHash(any(String.class)); + + mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/entry/abc/relations")).with(csrf())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.translations[0].lang").value(dummyOntologyItemRelations.translations().stream().toList().get(0).lang())) + .andExpect(jsonPath("$.translations[0].value").value(dummyOntologyItemRelations.translations().stream().toList().get(0).value())) + .andExpect(jsonPath("$.children[0].contextualizedTermcodeHash").value(dummyOntologyItemRelations.children().stream().toList().get(0).contextualizedTermcodeHash())) + .andExpect(jsonPath("$.children[0].name").value(dummyOntologyItemRelations.children().stream().toList().get(0).name())) + .andExpect(jsonPath("$.parents[0].contextualizedTermcodeHash").value(dummyOntologyItemRelations.parents().stream().toList().get(0).contextualizedTermcodeHash())) + .andExpect(jsonPath("$.parents[0].name").value(dummyOntologyItemRelations.parents().stream().toList().get(0).name())) + .andExpect(jsonPath("$.relatedTerms[0].contextualizedTermcodeHash").value(dummyOntologyItemRelations.relatedTerms().stream().toList().get(0).contextualizedTermcodeHash())) + .andExpect(jsonPath("$.relatedTerms[0].name").value(dummyOntologyItemRelations.relatedTerms().stream().toList().get(0).name())); + } + + @Test + public void testGetOntologyItemRelationsByHash_failsOnUnauthorized() throws Exception { + var dummyOntologyItemRelations = createDummyOntologyItemRelations(); + doReturn(dummyOntologyItemRelations).when(terminologyEsService).getOntologyItemRelationsByHash(any(String.class)); + + mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/entry/abc/relations")).with(csrf())) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser(roles = "DATAPORTAL_TEST_USER") + public void testGetOntologyItemByHash_succeeds() throws Exception { + var dummySearchResultEntry = createDummyEsSearchResultEntry(); + doReturn(dummySearchResultEntry).when(terminologyEsService).getSearchResultEntryByHash(any(String.class)); + + mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/entry/abc")).with(csrf())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(dummySearchResultEntry.id())) + .andExpect(jsonPath("$.name").value(dummySearchResultEntry.name())) + .andExpect(jsonPath("$.availability").value(dummySearchResultEntry.availability())) + .andExpect(jsonPath("$.context").value(dummySearchResultEntry.context())) + .andExpect(jsonPath("$.terminology").value(dummySearchResultEntry.terminology())) + .andExpect(jsonPath("$.termcode").value(dummySearchResultEntry.termcode())) + .andExpect(jsonPath("$.kdsModule").value(dummySearchResultEntry.kdsModule())) + .andExpect(jsonPath("$.selectable").value(dummySearchResultEntry.selectable())); + } + + @Test + public void testGetOntologyItemByHash_failsOnUnauthorized() throws Exception { + var dummySearchResultEntry = createDummyEsSearchResultEntry(); + doReturn(dummySearchResultEntry).when(terminologyEsService).getSearchResultEntryByHash(any(String.class)); + + mockMvc.perform(get(URI.create(PATH_API + PATH_TERMINOLOGY + "/entry/abc")).with(csrf())) + .andExpect(status().isUnauthorized()); + } + + private TermCode createTermCode() { + return TermCode.builder() + .code("LL2191-6") + .system("http://loinc.org") + .display("Geschlecht") + .version("1.0.0") + .build(); + } + + private UiProfile createUiProfile() { + return UiProfile.builder() + .name("test-ui-profile") + .attributeDefinitions(List.of(createAttributeDefinition())) + .valueDefinition(createAttributeDefinition()) + .timeRestrictionAllowed(true) + .build(); + } + + private AttributeDefinition createAttributeDefinition() { + return AttributeDefinition.builder() + .min(1.0) + .max(99.9) + .allowedUnits(List.of(createTermCode())) + .attributeCode(createTermCode()) + .type(ValueDefinitonType.CONCEPT) + .optional(false) + .referencedCriteriaSet("http://my.reference.criteria/set") + .referencedValueSet("http://my.reference.value/set") + .comparator(Comparator.EQUAL) + .precision(1.0) + .selectableConcepts(List.of(createTermCode())) + .build(); + } + + private List createCriteriaProfileDataList(List ids) { + List criteriaProfileDataList = new ArrayList<>(); + for (UUID uuid: ids) { + criteriaProfileDataList.add( + CriteriaProfileData.builder() + .id(uuid.toString()) + .context(createTermCode()) + .termCodes(List.of(createTermCode())) + .uiProfile(createUiProfile()) + .build() + ); + } + return criteriaProfileDataList; + } + + private EsSearchResult createDummyEsSearchResult(int totalHits) { + return EsSearchResult.builder() + .totalHits(totalHits) + .results(List.of(createDummyEsSearchResultEntry())) + .build(); + } + + private EsSearchResultEntry createDummyEsSearchResultEntry() { + return EsSearchResultEntry.builder() + .terminology("some-terminology") + .availability(100) + .context("some-context") + .id("abc-123") + .kdsModule("some-module") + .name("some-name") + .selectable(true) + .build(); + } + + private OntologyItemRelationsDocument createDummyOntologyItemRelations() { + return OntologyItemRelationsDocument.builder() + .relatedTerms(List.of(createDummyRelative())) + .translations(List.of(createDummyTranslation())) + .parents(List.of(createDummyRelative())) + .children(List.of(createDummyRelative())) + .build(); + } + + private Translation createDummyTranslation() { + return Translation.builder() + .lang("de") + .value("Lorem Ipsum") + .build(); + } + + private Relative createDummyRelative() { + return Relative.builder() + .contextualizedTermcodeHash(UUID.randomUUID().toString()) + .name("some-random-name") + .build(); + } + + private List createTermFilterList(String[] values) { + var termFilters = new ArrayList(); + for (String term : values) { + termFilters.add( + TermFilter.builder() + .name(term) + .type("selectable-concept") + .values(List.of(TermFilterValue.builder().label("baz").count(values.length).build())) + .build() + ); + } + termFilters.add(TermFilter.builder() + .type("boolean") + .name("availability") + .values(List.of()) + .build()); + return termFilters; + } +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 64ae012c..a2fd418f 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -1,6 +1,6 @@ spring: datasource: - url: "jdbc:tc:postgresql:15.1-alpine:///codex_ui_test" + url: "jdbc:tc:postgresql:16-alpine:///dataportal_test" security: config: use-keycloak: false @@ -11,9 +11,9 @@ management: enabled: false app: queryResultExpiryMinutes: 1 - keycloakAllowedRole: "FEASIBILITY_TEST_USER" - keycloakPowerRole: "FEASIBILITY_TEST_POWER" - keycloakAdminRole: "FEASIBILITY_TEST_ADMIN" + keycloakAllowedRole: "DATAPORTAL_TEST_USER" + keycloakPowerRole: "DATAPORTAL_TEST_POWER" + keycloakAdminRole: "DATAPORTAL_TEST_ADMIN" maxSavedQueriesPerUser: 2 broker: aktin: