From 32f6a9b633e9d75f080fb16d19c5f5d67b9a5d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Vuilleumier?= Date: Mon, 30 Sep 2024 09:49:20 +0200 Subject: [PATCH 1/5] Add acceptance tests --- .github/workflows/build.yaml | 44 +++--- .github/workflows/pull-request.yaml | 4 + Makefile | 18 ++- acceptance_tests/.gitignore | 3 + acceptance_tests/Dockerfile | 15 ++ acceptance_tests/README.md | 18 +++ acceptance_tests/pyproject.toml | 17 +++ acceptance_tests/tests/conftest.py | 12 ++ .../tests/test_cascaded_stores.py | 140 ++++++++++++++++++ acceptance_tests/tests/test_workspace.py | 23 +++ ci/requirements.txt | 1 + compose/acceptance.yml | 95 ++++++++++++ compose/compose.yml | 1 + 13 files changed, 373 insertions(+), 18 deletions(-) create mode 100644 acceptance_tests/.gitignore create mode 100644 acceptance_tests/Dockerfile create mode 100644 acceptance_tests/README.md create mode 100644 acceptance_tests/pyproject.toml create mode 100644 acceptance_tests/tests/conftest.py create mode 100644 acceptance_tests/tests/test_cascaded_stores.py create mode 100644 acceptance_tests/tests/test_workspace.py create mode 100644 ci/requirements.txt create mode 100644 compose/acceptance.yml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 6bf5d5013..4e2ed3a36 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -23,25 +23,35 @@ jobs: uses: actions/checkout@v2 with: submodules: recursive - - name: Setup Java - uses: actions/setup-java@v2 - with: - distribution: 'temurin' - java-version: '21' - cache: 'maven' + # - name: Setup Java + # uses: actions/setup-java@v2 + # with: + # distribution: 'temurin' + # java-version: '21' + # cache: 'maven' + - name: Install CI dependencies + run: python3 -m pip install --user --requirement=ci/requirements.txt - - name: Validate source code formatting - run: make lint + # - name: Validate source code formatting + # run: make lint - - name: Build and test - run: | - make install test + # - name: Build and test + # run: | + # make install test - - name: Build docker images - run: | - make build-image + # - name: Build docker images + # run: | + # make build-image - - name: Remove project jars from cached repository + - name: Run acceptance tests run: | - rm -rf ~/.m2/repository/org/geoserver - find ~/.m2/repository -name "*SNAPSHOT*" -type d | xargs rm -rf {} + make acceptance-tests + + - name: Print docker compose logs + run: (cd compose && c2cciutils-docker-logs) + if: always() + + # - name: Remove project jars from cached repository + # run: | + # rm -rf ~/.m2/repository/org/geoserver + # find ~/.m2/repository -name "*SNAPSHOT*" -type d | xargs rm -rf {} diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 2ff95bdac..58d5b3071 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -47,6 +47,10 @@ jobs: run: | make build-image + - name: Run acceptance tests + run: | + make acceptance-tests + - name: Remove project jars from cached repository run: | rm -rf ~/.m2/repository/org/geoserver diff --git a/Makefile b/Makefile index 369298356..e62b6cdb0 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,13 @@ all: install test build-image -TAG=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout` +TAG=$(shell mvn help:evaluate -Dexpression=project.version -q -DforceStdout) COSIGN_PASSWORD := $(COSIGN_PASSWORD) +COMPOSE_PGCONFIG_OPTIONS ?= -f compose.yml -f catalog-pgconfig.yml +COMPOSE_DATADIR_OPTIONS ?= -f compose.yml -f catalog-datadir.yml +COMPOSE_ACCEPTANCE_PGCONFIG_OPTIONS ?= $(COMPOSE_PGCONFIG_OPTIONS) -f acceptance.yml +COMPOSE_ACCEPTANCE_DATADIR_OPTIONS ?= $(COMPOSE_DATADIR_OPTIONS) -f acceptance.yml +UID=$(shell id -u) +GID=$(shell id -g) clean: ./mvnw clean @@ -78,3 +84,13 @@ verify-image: fi; \ done' +.PHONY: build-acceptance +build-acceptance: + docker build --tag=acceptance:$(TAG) acceptance_tests + +.PHONY: acceptance-tests +acceptance-tests: +acceptance-tests: build-acceptance + (cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) up -d) + sleep 30 + (cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) exec -T acceptance pytest . -vvv --color=yes) diff --git a/acceptance_tests/.gitignore b/acceptance_tests/.gitignore new file mode 100644 index 000000000..c82b9e662 --- /dev/null +++ b/acceptance_tests/.gitignore @@ -0,0 +1,3 @@ +# ignore all python cache files recursively +**/__pycache__/ +poetry.lock diff --git a/acceptance_tests/Dockerfile b/acceptance_tests/Dockerfile new file mode 100644 index 000000000..0eead3f8b --- /dev/null +++ b/acceptance_tests/Dockerfile @@ -0,0 +1,15 @@ +FROM ubuntu:24.04 AS base + +RUN apt-get update \ + && apt-get upgrade --assume-yes \ + && apt-get install --assume-yes --no-install-recommends \ + vim curl jq libmagic1 zip python3-pip libpq-dev python3-dev gcc \ + && rm -rf /var/lib/apt/lists/* \ + && rm /usr/lib/python*/EXTERNALLY-MANAGED + +COPY . /acceptance_tests + +WORKDIR /acceptance_tests +RUN python3 -m pip install --disable-pip-version-check . + +CMD ["sleep", "infinity"] diff --git a/acceptance_tests/README.md b/acceptance_tests/README.md new file mode 100644 index 000000000..50ba5b580 --- /dev/null +++ b/acceptance_tests/README.md @@ -0,0 +1,18 @@ +# GeoServer Cloud acceptance tests + +## Requirements + +[Poetry](https://python-poetry.org/docs/#installing-with-the-official-installer) + +## Installation + +```shell +poetry install +``` + +# Run the tests +First start the docker composition then run: + +```shell +GEOSERVER_URL=http://localhost:9090/geoserver/cloud poetry run pytest -vvv . +``` \ No newline at end of file diff --git a/acceptance_tests/pyproject.toml b/acceptance_tests/pyproject.toml new file mode 100644 index 000000000..e1a076859 --- /dev/null +++ b/acceptance_tests/pyproject.toml @@ -0,0 +1,17 @@ +[tool.poetry] +name = "acceptance-tests" +version = "0.1.0" +description = "todo" +authors = ["todo"] +readme = "README.md" +packages = [{ include = "tests" }] + +[tool.poetry.dependencies] +python = "^3.10" +pytest = "^8.3.3" +geoservercloud = "^0.2.5" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/acceptance_tests/tests/conftest.py b/acceptance_tests/tests/conftest.py new file mode 100644 index 000000000..02aefbbf5 --- /dev/null +++ b/acceptance_tests/tests/conftest.py @@ -0,0 +1,12 @@ +import os + +import pytest +from geoservercloud import GeoServerCloud + + +GEOSERVER_URL = os.getenv("GEOSERVER_URL", "http://gateway:8080/geoserver/cloud") + + +@pytest.fixture(scope="module") +def geoserver(): + yield GeoServerCloud(GEOSERVER_URL) diff --git a/acceptance_tests/tests/test_cascaded_stores.py b/acceptance_tests/tests/test_cascaded_stores.py new file mode 100644 index 000000000..a5061c78e --- /dev/null +++ b/acceptance_tests/tests/test_cascaded_stores.py @@ -0,0 +1,140 @@ +import json + +import pytest +from conftest import GEOSERVER_URL +from geoservercloud import GeoServerCloud + +WORKSPACE = "test_cascade" +WMS_STORE = "test_cascaded_wms_store" +WMS_URL = "https://wms.geo.admin.ch/?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetCapabilities" +WMS_LAYER = "ch.swisstopo.swissboundaries3d-gemeinde-flaeche.fill" +WMTS_STORE = "test_cascaded_wmts_store" +WMTS_URL = "https://wmts.geo.admin.ch/EPSG/4326/1.0.0/WMTSCapabilities.xml" +WMTS_LAYER = "ch.swisstopo.pixelkarte-grau" + + +def create_cascaded_wms_store_payload(): + return { + "wmsStore": { + "name": WMS_STORE, + "type": "WMS", + "enabled": "true", + "workspace": {"name": WORKSPACE}, + "metadata": {"entry": {"@key": "useConnectionPooling", "$": "true"}}, + "_default": "false", + "disableOnConnFailure": "false", + "capabilitiesURL": WMS_URL, + "maxConnections": 6, + "readTimeout": 60, + "connectTimeout": 30, + } + } + + +def delete_wms_store(geoserver): + geoserver.delete_request( + f"/rest/workspaces/{WORKSPACE}/wmsstores/{WMS_STORE}?recurse=true" + ) + + +def delete_wmts_store(geoserver): + geoserver.delete_request( + f"/rest/workspaces/{WORKSPACE}/wmtsstores/{WMTS_STORE}?recurse=true" + ) + + +@pytest.fixture(scope="module") +def geoserver(): + geoserver = GeoServerCloud(url=GEOSERVER_URL) + geoserver.create_workspace(WORKSPACE, set_default_workspace=True) + geoserver.publish_workspace(WORKSPACE) + yield geoserver + # geoserver.delete_workspace(WORKSPACE) + + +def test_cascaded_wms(geoserver): + format = "image/jpeg" + + # Create WMS store + payload = create_cascaded_wms_store_payload() + response = geoserver.post_request( + f"/rest/workspaces/{WORKSPACE}/wmsstores", json=payload + ) + assert response.status_code == 201 + + # Publish layer + payload = { + "wmsLayer": { + "name": WMS_LAYER, + } + } + response = geoserver.post_request( + f"/rest/workspaces/{WORKSPACE}/wmsstores/{WMS_STORE}/wmslayers", + json=payload, + ) + assert response.status_code == 201 + + # Perform GetMap request + response = geoserver.get_map( + layers=[WMS_LAYER], + bbox=(2590000, 1196000, 2605000, 1203000), + size=(10, 10), + format=format, + ) + assert response.info().get("Content-Type") == format + + # Perform GetFeatureInfo request + response = geoserver.get_feature_info( + layers=[WMS_LAYER], + bbox=(2599999.5, 1199999.5, 2600000.5, 1200000.5), + size=(40, 40), + info_format="application/json", + xy=(20, 20), + ) + + # Due to conflicting formats, the forwarding of GetFeatureInfo requests from map.geo.admin (MapServer) + # through GeoServer is not possible as of 2.25.0. + # See https://sourceforge.net/p/geoserver/mailman/message/30757977/ + data = json.loads(response.read().decode("utf-8")) + assert data.get("features") == [] + + delete_wms_store(geoserver) + + +def test_cascaded_wmts(geoserver): + format = "image/jpeg" + + # Create WMTS store + response = geoserver.create_wmts_store( + WORKSPACE, + WMTS_STORE, + capabilities="https://wmts.geo.admin.ch/EPSG/4326/1.0.0/WMTSCapabilities.xml", + ) + assert response.status_code == 201 + + # Publish layer (GeoServer) + response = geoserver.create_wmts_layer(WORKSPACE, WMTS_STORE, WMTS_LAYER) + assert response.status_code == 201 + response = geoserver.get_request( + f"/rest/workspaces/{WORKSPACE}/wmtsstores/{WMTS_STORE}/layers/{WMTS_LAYER}.json" + ) + assert response.status_code == 200 + + # Publish the layer in GWC + response = geoserver.publish_gwc_layer(WORKSPACE, WMTS_LAYER) + assert response.status_code == 200 + + # Perform GetTile request (GWC) + response = geoserver.get_tile( + layer=f"{WORKSPACE}:{WMTS_LAYER}", + tile_matrix_set="EPSG:4326", + tile_matrix="EPSG:4326:9", + row=122, + column=534, + format=format, + ) + assert response.info().get("Content-Type") == format + + response = geoserver.delete_request(f"/gwc/rest/layers/{WORKSPACE}:{WMTS_LAYER}") + assert response.status_code == 200 + delete_wmts_store(geoserver) diff --git a/acceptance_tests/tests/test_workspace.py b/acceptance_tests/tests/test_workspace.py new file mode 100644 index 000000000..23a8bc9bc --- /dev/null +++ b/acceptance_tests/tests/test_workspace.py @@ -0,0 +1,23 @@ +def test_create_get_and_delete_workspace(geoserver): + workspace = "test_create_workspace" + response = geoserver.create_workspace(workspace) + assert response.status_code == 201 + response = geoserver.get_request(f"/rest/workspaces/{workspace}.json") + assert response.status_code == 200 + response = geoserver.publish_workspace(workspace) + assert response.status_code == 200 + response = geoserver.delete_workspace(workspace) + assert response.status_code == 200 + + +def test_update_workspace(geoserver): + workspace = "update_workspace" + response = geoserver.create_workspace(workspace, isolated=True) + assert response.status_code == 201 + response = geoserver.get_request(f"/rest/workspaces/{workspace}.json") + assert response.json().get("workspace").get("isolated") == True + response = geoserver.create_workspace(workspace, isolated=False) + assert response.status_code == 200 + response = geoserver.get_request(f"/rest/workspaces/{workspace}.json") + assert response.json().get("workspace").get("isolated") == False + geoserver.delete_workspace(workspace) diff --git a/ci/requirements.txt b/ci/requirements.txt new file mode 100644 index 000000000..101fbee83 --- /dev/null +++ b/ci/requirements.txt @@ -0,0 +1 @@ +c2cciutils[checks]==1.6.18 diff --git a/compose/acceptance.yml b/compose/acceptance.yml new file mode 100644 index 000000000..a8f48dd82 --- /dev/null +++ b/compose/acceptance.yml @@ -0,0 +1,95 @@ +services: + acceptance: + image: acceptance:${TAG} + depends_on: + init-datadir: + condition: service_completed_successfully + gateway: + condition: service_healthy + discovery: + condition: service_healthy + config: + condition: service_healthy + wms: + condition: service_healthy + # TODO: add wcs and wps + wfs: + condition: service_healthy + gwc: + condition: service_healthy + webui: + condition: service_started + rest: + condition: service_healthy + acl: + condition: service_started + + # For github CI + acl: + deploy: + resources: + limits: + cpus: "1.0" + memory: 512M + gateway: + deploy: + resources: + limits: + cpus: "1.0" + memory: 512M + discovery: + deploy: + resources: + limits: + cpus: "1.0" + memory: 512M + config: + deploy: + resources: + limits: + cpus: "1.0" + memory: 512M + wms: + deploy: + resources: + limits: + cpus: "1.0" + memory: 512M + healthcheck: + retries: 20 + wfs: + deploy: + resources: + limits: + cpus: "1.0" + memory: 512M + healthcheck: + retries: 20 + rest: + deploy: + resources: + limits: + cpus: "1.0" + memory: 512M + healthcheck: + retries: 20 + gwc: + deploy: + resources: + limits: + cpus: "1.0" + memory: 512M + healthcheck: + retries: 20 + webui: + deploy: + resources: + limits: + cpus: "1.0" + memory: 512M + rabbitmq: + deploy: + resources: + limits: + cpus: "1.0" + memory: 512M diff --git a/compose/compose.yml b/compose/compose.yml index 24bf33204..1a272b5d4 100644 --- a/compose/compose.yml +++ b/compose/compose.yml @@ -8,6 +8,7 @@ volumes: type: none o: bind device: $PWD/../config + driver: local x-gs-dependencies: &gs-dependencies rabbitmq: From fe35e23d45fb3d4ef254dc525fde6f13c70f3e84 Mon Sep 17 00:00:00 2001 From: Andrea Borghi Date: Tue, 1 Oct 2024 11:41:36 +0200 Subject: [PATCH 2/5] Add geodatabase for acceptance tests --- .github/workflows/build.yaml | 4 ++++ Makefile | 4 ++++ compose/acceptance.yml | 18 ++++++++++++++++++ .../001_create_schemas.sql | 3 +++ 4 files changed, 29 insertions(+) create mode 100755 compose/acceptance_pg_entrypoint/001_create_schemas.sql diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4e2ed3a36..e387d320e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -51,6 +51,10 @@ jobs: run: (cd compose && c2cciutils-docker-logs) if: always() + - name: Cleanup acceptance tests + run: | + make stop-acceptance-tests + # - name: Remove project jars from cached repository # run: | # rm -rf ~/.m2/repository/org/geoserver diff --git a/Makefile b/Makefile index e62b6cdb0..7383feb16 100644 --- a/Makefile +++ b/Makefile @@ -94,3 +94,7 @@ acceptance-tests: build-acceptance (cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) up -d) sleep 30 (cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) exec -T acceptance pytest . -vvv --color=yes) + +.PHONY: stop-acceptance-tests +stop-acceptance-tests: build-acceptance + (cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) down -v) \ No newline at end of file diff --git a/compose/acceptance.yml b/compose/acceptance.yml index a8f48dd82..e6d35fa98 100644 --- a/compose/acceptance.yml +++ b/compose/acceptance.yml @@ -1,7 +1,25 @@ services: + geodatabase: + image: imresamu/postgis:15-3.4 + environment: + POSTGRES_DB: geodata + POSTGRES_USER: geodata + POSTGRES_PASSWORD: geodata + restart: always + volumes: + - ./acceptance_pg_entrypoint:/docker-entrypoint-initdb.d:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U geodata"] + interval: 30s + timeout: 10s + retries: 5 + acceptance: image: acceptance:${TAG} + user: ${GS_USER} depends_on: + geodatabase: + condition: service_healthy init-datadir: condition: service_completed_successfully gateway: diff --git a/compose/acceptance_pg_entrypoint/001_create_schemas.sql b/compose/acceptance_pg_entrypoint/001_create_schemas.sql new file mode 100755 index 000000000..230f04cfc --- /dev/null +++ b/compose/acceptance_pg_entrypoint/001_create_schemas.sql @@ -0,0 +1,3 @@ +\c geodata +CREATE SCHEMA IF NOT EXISTS test1; +CREATE SCHEMA IF NOT EXISTS test2; \ No newline at end of file From 9b290afcc6d9ec31f035514afb8f15fa1232716a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Vuilleumier?= Date: Tue, 1 Oct 2024 13:33:11 +0200 Subject: [PATCH 3/5] Add more acceptance tests --- Makefile | 2 +- acceptance_tests/pyproject.toml | 2 + acceptance_tests/tests/conftest.py | 52 ++- acceptance_tests/tests/lib/utils.py | 15 + .../tests/resources/getmap_expected.png | Bin 0 -> 168 bytes .../default_value/language_None_expected.png | Bin 0 -> 2541 bytes .../default_value/language__expected.png | Bin 0 -> 1916 bytes .../default_value/language_de_expected.png | Bin 0 -> 2209 bytes .../default_value/language_fr_expected.png | Bin 0 -> 2541 bytes .../default_value/language_it_expected.png | Bin 0 -> 670 bytes .../language_None_expected.png | Bin 0 -> 2541 bytes .../no_default_value/language__expected.png | Bin 0 -> 670 bytes .../no_default_value/language_it_expected.png | Bin 0 -> 670 bytes .../default_value/language_None_expected.png | Bin 0 -> 1916 bytes .../default_value/language__expected.png | Bin 0 -> 1916 bytes .../default_value/language_de_expected.png | Bin 0 -> 2209 bytes .../default_value/language_fr_expected.png | Bin 0 -> 2541 bytes .../default_value/language_it_expected.png | Bin 0 -> 670 bytes .../language_None_expected.png | Bin 0 -> 670 bytes .../no_default_value/language__expected.png | Bin 0 -> 670 bytes .../no_default_value/language_it_expected.png | Bin 0 -> 670 bytes .../tests/resources/localized_labels.sld | 36 ++ .../tests/resources/localized_no_default.sld | 37 ++ .../resources/localized_with_default.sld | 37 ++ .../tests/resources/wfs_payload.xml | 16 + acceptance_tests/tests/test_datastore.py | 24 + acceptance_tests/tests/test_gwc.py | 47 ++ acceptance_tests/tests/test_i18n.py | 417 ++++++++++++++++++ acceptance_tests/tests/test_wfs.py | 76 ++++ acceptance_tests/tests/test_wms.py | 128 ++++++ .../001_create_schemas.sql | 3 +- 31 files changed, 888 insertions(+), 4 deletions(-) create mode 100644 acceptance_tests/tests/lib/utils.py create mode 100644 acceptance_tests/tests/resources/getmap_expected.png create mode 100644 acceptance_tests/tests/resources/labels/default_locale/default_value/language_None_expected.png create mode 100644 acceptance_tests/tests/resources/labels/default_locale/default_value/language__expected.png create mode 100644 acceptance_tests/tests/resources/labels/default_locale/default_value/language_de_expected.png create mode 100644 acceptance_tests/tests/resources/labels/default_locale/default_value/language_fr_expected.png create mode 100644 acceptance_tests/tests/resources/labels/default_locale/default_value/language_it_expected.png create mode 100644 acceptance_tests/tests/resources/labels/default_locale/no_default_value/language_None_expected.png create mode 100644 acceptance_tests/tests/resources/labels/default_locale/no_default_value/language__expected.png create mode 100644 acceptance_tests/tests/resources/labels/default_locale/no_default_value/language_it_expected.png create mode 100644 acceptance_tests/tests/resources/labels/no_default_locale/default_value/language_None_expected.png create mode 100644 acceptance_tests/tests/resources/labels/no_default_locale/default_value/language__expected.png create mode 100644 acceptance_tests/tests/resources/labels/no_default_locale/default_value/language_de_expected.png create mode 100644 acceptance_tests/tests/resources/labels/no_default_locale/default_value/language_fr_expected.png create mode 100644 acceptance_tests/tests/resources/labels/no_default_locale/default_value/language_it_expected.png create mode 100644 acceptance_tests/tests/resources/labels/no_default_locale/no_default_value/language_None_expected.png create mode 100644 acceptance_tests/tests/resources/labels/no_default_locale/no_default_value/language__expected.png create mode 100644 acceptance_tests/tests/resources/labels/no_default_locale/no_default_value/language_it_expected.png create mode 100644 acceptance_tests/tests/resources/localized_labels.sld create mode 100644 acceptance_tests/tests/resources/localized_no_default.sld create mode 100644 acceptance_tests/tests/resources/localized_with_default.sld create mode 100644 acceptance_tests/tests/resources/wfs_payload.xml create mode 100644 acceptance_tests/tests/test_datastore.py create mode 100644 acceptance_tests/tests/test_gwc.py create mode 100755 acceptance_tests/tests/test_i18n.py create mode 100644 acceptance_tests/tests/test_wfs.py create mode 100644 acceptance_tests/tests/test_wms.py diff --git a/Makefile b/Makefile index 7383feb16..cc754aa04 100644 --- a/Makefile +++ b/Makefile @@ -97,4 +97,4 @@ acceptance-tests: build-acceptance .PHONY: stop-acceptance-tests stop-acceptance-tests: build-acceptance - (cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) down -v) \ No newline at end of file + (cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) down -v) diff --git a/acceptance_tests/pyproject.toml b/acceptance_tests/pyproject.toml index e1a076859..956567cfa 100644 --- a/acceptance_tests/pyproject.toml +++ b/acceptance_tests/pyproject.toml @@ -9,7 +9,9 @@ packages = [{ include = "tests" }] [tool.poetry.dependencies] python = "^3.10" pytest = "^8.3.3" +psycopg2-binary = "^2.9.9" geoservercloud = "^0.2.5" +sqlalchemy = "^2.0.35" [build-system] diff --git a/acceptance_tests/tests/conftest.py b/acceptance_tests/tests/conftest.py index 02aefbbf5..e450c66ca 100644 --- a/acceptance_tests/tests/conftest.py +++ b/acceptance_tests/tests/conftest.py @@ -1,12 +1,60 @@ import os +from pathlib import Path import pytest +import sqlalchemy from geoservercloud import GeoServerCloud - GEOSERVER_URL = os.getenv("GEOSERVER_URL", "http://gateway:8080/geoserver/cloud") +RESOURCE_DIR = Path(__file__).parent / "resources" +# Database connection +PGHOST = "geodatabase" +PGPORT = 5432 +PGDATABASE = "geodata" +PGUSER = "geodata" +PGPASSWORD = "geodata" +PGSCHEMA = "test1" +WORKSPACE = "test_workspace" +DATASTORE = "test_datastore" + + +@pytest.fixture(scope="session", autouse=True) +def engine(): + yield sqlalchemy.create_engine( + f"postgresql://{PGUSER}:{PGPASSWORD}@{PGHOST}:{PGPORT}/{PGDATABASE}", + ) + + +@pytest.fixture(scope="session", autouse=True) +def db_session(engine): + with engine.connect() as connection: + connection.execute( + sqlalchemy.sql.text(f"CREATE SCHEMA IF NOT EXISTS {PGSCHEMA}") + ) + connection.execute(sqlalchemy.sql.text(f"SET SEARCH_PATH = {PGSCHEMA}")) + connection.commit() + yield connection + connection.execute( + sqlalchemy.sql.text(f"DROP SCHEMA IF EXISTS {PGSCHEMA} CASCADE") + ) + connection.commit() @pytest.fixture(scope="module") def geoserver(): - yield GeoServerCloud(GEOSERVER_URL) + geoserver = GeoServerCloud(GEOSERVER_URL) + geoserver.recreate_workspace(WORKSPACE, set_default_workspace=True) + geoserver.create_pg_datastore( + workspace=WORKSPACE, + datastore=DATASTORE, + pg_host=PGHOST, + pg_port=PGPORT, + pg_db=PGDATABASE, + pg_user=PGUSER, + pg_password=PGPASSWORD, + pg_schema=PGSCHEMA, + set_default_datastore=True, + ) + geoserver.publish_workspace(WORKSPACE) + yield geoserver + geoserver.delete_workspace(WORKSPACE) diff --git a/acceptance_tests/tests/lib/utils.py b/acceptance_tests/tests/lib/utils.py new file mode 100644 index 000000000..7212866d5 --- /dev/null +++ b/acceptance_tests/tests/lib/utils.py @@ -0,0 +1,15 @@ +from pathlib import Path + + +def write_actual_image(response, tag): + file = Path(f"/tmp/{tag}_actual.png") + file.parent.mkdir(parents=True, exist_ok=True) + with open(file, "wb") as fs: + fs.write(response.read()) + + +def compare_images(dir, tag): + actual = f"/tmp/{tag}_actual.png" + expected = f"{dir}/{tag}_expected.png" + with open(actual, "rb") as fs1, open(expected, "rb") as fs2: + assert fs1.read() == fs2.read() diff --git a/acceptance_tests/tests/resources/getmap_expected.png b/acceptance_tests/tests/resources/getmap_expected.png new file mode 100644 index 0000000000000000000000000000000000000000..c93b6740a5aa100fcb06b1e0a243cd00643439eb GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1SFZ~=vx7)d`}n0kczmsmmIkcDDb#kyz~E3 z=Ji5T`5?`j!o>!BmAPM+hEA`#{pkFYrPHc>56sW*Ewj`4DiUe^zkAoJ17Y$JF!UC+}#Vdu}b9@#yo+KgS;3;Za-CR#55l3Yj5iI4Nzq?E-sypw$eXu6{1- HoD!MUxt{B~KlgLppZofJ?i5>VGf|i{3O;2fhpd4XRlQunxBv`DM-zArH-Wi0 znZVt|5{4|nxJsEUzC1}dIgej{)7}KGCis6S=!Jg#-Zt;Fdt*ijqTytvF+ zxX9ff%j4RdB9p+Sg+V1cUVtKD+*oZt4B#wsR#*2bSXBi*xT#_@4|T$Q{5BA1>y7rDFqo=Flo z;@KPu-Z6~&W)wbuSsJj`@a%EZt$8Uo(A{7E{y^wn*Q2w}EClw>dG#b89l5M1IM|yi zX(|W0-mxpSM2wywX&Hsgp>^O4n__yuQE|#~6)1cbHy@r`+m)IvK||l7hlO>Mg>SEX z(4X*Vi`0j2GPhOPETBZTmd5)3!H07JuULqI`0)pY+kgYQ z9=617uOd3Vl8@XB?$7*vePqAqTh#vUI;Yj-KA#b;ned?+(J-io1zZfY_Uhx=K3*(p z6lfG<{ovOg6z{~*vnL1kj1--VYDPKNTwP-oX!~Az?$$6=;C(1b_Ewj9xUQIAR$b?U zf;t^S6b2o0NL%GSN}>uu1}Hx0CeyRs@b*JwP6Eq{sw3et_WH^*CkfN$)c3Q_Lj3O* zBM_2 z2yX~@9-98sR@jyK4zDC=G^gw3kX67 z;}>FH#xa?04OL9?7rU3&hrKK$P{f`>2wDH;`(*O+=k~jHsrxVm_309E5R0mJ4Xk<$ zFNjUYcxQ@|59{hY$pJS9=H}31LUZ}l%Jl_O^TXf6P*1Wq z3ji`vq=81l+Sk7*8j6_ha(H%b_1mG2H0PtiN82Kn@P*->zEExi1)PX2yOh!G1TeEM z3aTMf*nuj_C|q_bOLS$Vlt0Mvpssx0Um4A3%%w7r^R^U>5($zSil8`10i!1c!wyH? zOBntt8nG^eKoewm&1OpI6;JvR+Lo8Ja?C01pzRm27b~rc3`Z#*>c2)|>YSN$)b~|i z)$ZLPo3n^w6s&u9akXC36AFPF@)qMUf*6^;17}Tc+zB!}=@eUR-HmkIPRw#H@}yiJ zbfh;igHJgY9 z7TLYzI?iG}A8BN1?F~i_$PCyEuEYp8pjJfmJWU*c$~~#Z7(P#sk1#X}XA1&Qak~#P vML>=){9P8qO$4A6R8Xe1y8QpnH{$tO8vMB1hgsi%7zZ-HXnmmu??w6#X`+kW literal 0 HcmV?d00001 diff --git a/acceptance_tests/tests/resources/labels/default_locale/default_value/language__expected.png b/acceptance_tests/tests/resources/labels/default_locale/default_value/language__expected.png new file mode 100644 index 0000000000000000000000000000000000000000..3272ee1843f3e5a7fe65bfef2308c39fd8e20693 GIT binary patch literal 1916 zcmd^A`#aMM9R7x|GN&d-R=;qE)s}QC+I22iHjh0cO z%|fTngXGd=l$os~!(5ilC3CE^pUxj~&M%+m{pIt#&-?kj&-><}knZ3O&TGwHk-(YN%(!h4NAW&~fo_J%LUNnJqho@C(|0tI_%zZqsICtt)c@@U<8eeDfCgnco6IP*()`1n-UKM#xNhMm>AAl4&^_ zJ2IXjl<9?Y`|MNKhL(dw7=p}T`MQx7Ok8~Y-q7gOc8yKnvPfPUx=H4lq)N@IR9)E4 zD}Y8N2u7U%CQURDo!c`rqcjkcM}g*B0K`TICN2XYDf*dnJ2Oe$>;89Zs`O=3Ct{j% zVw%qk7<(xmstfI_bV!}~_>?fj%2(`)PMGSa%jNRvp*r&X9hHjMSW{iSwYGs#J z%F?9SWjlz3!rFE)7(9lLc@Bm^!E<%bWTLjV_~}6sNy~ds$W4rjjARA9K>8Or)Ke&w ziergOOG}T}KJL39N?%=?mGs*A~X6Bwv zL>CSm);Db>8L08aq>_7w&~b zs-_-SuTRzk@uzMZ9cZ_tLPK9BZ3boRv~-l;hio;cDtkLS-&4kOaRh>(9{WbQI8EMu zRKB8!su)r0e1d5xIS|^4|6snx^p-5m3gUy&=mkm7whG?*#W-*8{DQiWmd9g0^nF&4 zO$E6a_1!V;TYY0=Bc1Lh=!_G!PIb#Ds8WhgGdtwUMDMIr+HdT!)yRn9eng)T9WCY2 z+*g&#@JD+uSt#W4+}pR~^GN!-85Xu`Y0)}t9M8+JK}!+cao`N+WYb=fBgw+VOr2r!l7CHVUq>BKr$hhvDh&S(b3j61C4j* z8d?Qk5L&5*6VOan_-%W>{!t2QM-4J@jee@s#4+t`B4vJlo~M8dD;!f65a7bXCYw49UP2;~ zYFFr^UpCR^ScV|5*B{@La{{Bn67SOK^c0x!yD#$bcPV1(BI-aCt+Jq*j4Q1!G1?z| zmUDT$WM9yN;AU#xlq2iZKFl-MZEkOC6oK0oX`EHD`r-VOPM2}Z>|X^QA=Z*BG;(kguKh28?)yAG~rhiQ+v2|&toa-CgvXOgJq@7Ud& zjyoUn@xfJV6&@T2H!d~SK;X>xZU7epkpDdSH_dePTImra%0=C(fQK8>mF0Z?+CO*Z Bdw2i< literal 0 HcmV?d00001 diff --git a/acceptance_tests/tests/resources/labels/default_locale/default_value/language_de_expected.png b/acceptance_tests/tests/resources/labels/default_locale/default_value/language_de_expected.png new file mode 100644 index 0000000000000000000000000000000000000000..eb3c686304b3d0e456bdf4fe7ee6e855bab3b54b GIT binary patch literal 2209 zcmeHJ`&W|b7XH*B8B0pjJZZJGOl{OAqeE#oMH?cMP!SacGcTEjG^V0?S9Fe*Q<~Sj zBwi*$E)_!K^@vTjH0+{?iHb2!IfzW&FjK(u{WgEW`RSZ@?e(trvi92hdG_9vzXAZDF=#IjTnb5570eh6GkZy)(<}~C_m?#M zR_3L!tBP_kIs9g6S4ryYjGnwyCmYkK8}8^&qR6_a0qm#L)vP%6RPE1sX|9v%$ERoH zty$}HQ>3`8(X}~6q~=4OzQYHmdqGRm?MA>z10YKPNR2swoB=NGfn?v^<*s)blKs{d zs6^>m+Ztkfg7vHuVZbL}0NSr-b`1|eYYnnf%>ZP=|DM}MyiPU#jH=r>k-GTD)Nzsj zR{INeH3+h_ugGd?89&fRk#&A+@KvWx8$i$#-qf9Z+Clv+n>xx13uFb+`zj6+2YjYA z6a(526J4uoYGqALjf5RC zqhOa7U3hS6vMcS&-ZM92ti!s~(PMVF?v#Z~Z|glGUjD4jfUm3HHKNqBBjfE!aq*Tj zSytAftifT*!uux?`I{|ZS`M*8fP3VZC6wnF7{-bj#eo}<2c5SK{H4>bdzU*^c^lfD zue|FG9SMJ4V4QQPqIYPHVs?!;H{ zhIs@U$O#=|_%@pC`yqXGUPzF2rBQpb^#~IMu-$8`3lS|7PoumaoXQZ-)VN-6Efx&S zYaO8$2SXihO|~ahAEl6jY$0@!&;28hew^-`?kh^$&~>RB*&bX{S9_wOVH1JDHih16 z`s6Zbq}d`DRlywtIZp7k19158Kd^gl8S;n>tPObxCvkb|o}-ViYBj|(utHc>oP=cp zkLBG^&u%idOxLXXUKW5nDOt_uJc0yQ$7+vLZ}v<1ao>|VgUY~IL(1H9AC0XLF*({5 z)i_Actc2Y{4Up6Y--$k&zG2i0)^dEAjt*a2R!B&<16V=6yu8Qa&iXb__1=FcDu$9Z z!%f2tXEw)ep`rbjDx)Ep1=obRkf?#u;+@V=z0T>EcPyL zPIEasoC#)@K{ns^f?6k!3w#WbWE4B+muxR+(hBbe`<`V!CVRU*MqFyr4l=_+yt(mB zukE8$Fe5!=@%80DeVbVv(i|5-mM0!-9r-*jf3A}9;p1*iiYX`=TE^Gwgkv0tpVJH8E~Z%rw8-jx+OZ5R(F%7X!Xj zNf*aXC&8mdZ(k_Dr?HXQ3uTjIMMX zFd=#tY}tvpT1gt!-W2W%9dVBT-JRv68*xVo@oa*EI4NY3>_^nf(H3hRpG}_X?sD7W zdC$8YRYvsLo@_0?#(MVHegR-Xo4ez S1{&CU0O&IqFAnlz?tcJG047lY literal 0 HcmV?d00001 diff --git a/acceptance_tests/tests/resources/labels/default_locale/default_value/language_fr_expected.png b/acceptance_tests/tests/resources/labels/default_locale/default_value/language_fr_expected.png new file mode 100644 index 0000000000000000000000000000000000000000..3c3b1829fb537deba92616838a58dbfcbfccef69 GIT binary patch literal 2541 zcmeHJ`8(8WAD&?hCd*XljBzXpV=237q+v!S%duyTtVNhGvXsymjXe%wGzQTy5|ivA z981C+l895vGGm*vg%0t4-+$x1-d~>Uxt{B~KlgLppZofJ?i5>VGf|i{3O;2fhpd4XRlQunxBv`DM-zArH-Wi0 znZVt|5{4|nxJsEUzC1}dIgej{)7}KGCis6S=!Jg#-Zt;Fdt*ijqTytvF+ zxX9ff%j4RdB9p+Sg+V1cUVtKD+*oZt4B#wsR#*2bSXBi*xT#_@4|T$Q{5BA1>y7rDFqo=Flo z;@KPu-Z6~&W)wbuSsJj`@a%EZt$8Uo(A{7E{y^wn*Q2w}EClw>dG#b89l5M1IM|yi zX(|W0-mxpSM2wywX&Hsgp>^O4n__yuQE|#~6)1cbHy@r`+m)IvK||l7hlO>Mg>SEX z(4X*Vi`0j2GPhOPETBZTmd5)3!H07JuULqI`0)pY+kgYQ z9=617uOd3Vl8@XB?$7*vePqAqTh#vUI;Yj-KA#b;ned?+(J-io1zZfY_Uhx=K3*(p z6lfG<{ovOg6z{~*vnL1kj1--VYDPKNTwP-oX!~Az?$$6=;C(1b_Ewj9xUQIAR$b?U zf;t^S6b2o0NL%GSN}>uu1}Hx0CeyRs@b*JwP6Eq{sw3et_WH^*CkfN$)c3Q_Lj3O* zBM_2 z2yX~@9-98sR@jyK4zDC=G^gw3kX67 z;}>FH#xa?04OL9?7rU3&hrKK$P{f`>2wDH;`(*O+=k~jHsrxVm_309E5R0mJ4Xk<$ zFNjUYcxQ@|59{hY$pJS9=H}31LUZ}l%Jl_O^TXf6P*1Wq z3ji`vq=81l+Sk7*8j6_ha(H%b_1mG2H0PtiN82Kn@P*->zEExi1)PX2yOh!G1TeEM z3aTMf*nuj_C|q_bOLS$Vlt0Mvpssx0Um4A3%%w7r^R^U>5($zSil8`10i!1c!wyH? zOBntt8nG^eKoewm&1OpI6;JvR+Lo8Ja?C01pzRm27b~rc3`Z#*>c2)|>YSN$)b~|i z)$ZLPo3n^w6s&u9akXC36AFPF@)qMUf*6^;17}Tc+zB!}=@eUR-HmkIPRw#H@}yiJ zbfh;igHJgY9 z7TLYzI?iG}A8BN1?F~i_$PCyEuEYp8pjJfmJWU*c$~~#Z7(P#sk1#X}XA1&Qak~#P vML>=){9P8qO$4A6R8Xe1y8QpnH{$tO8vMB1hgsi%7zZ-HXnmmu??w6#X`+kW literal 0 HcmV?d00001 diff --git a/acceptance_tests/tests/resources/labels/default_locale/default_value/language_it_expected.png b/acceptance_tests/tests/resources/labels/default_locale/default_value/language_it_expected.png new file mode 100644 index 0000000000000000000000000000000000000000..84cc656581abc71234ec715a66ff25c19afdb691 GIT binary patch literal 670 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!Q#hD_Uxt{B~KlgLppZofJ?i5>VGf|i{3O;2fhpd4XRlQunxBv`DM-zArH-Wi0 znZVt|5{4|nxJsEUzC1}dIgej{)7}KGCis6S=!Jg#-Zt;Fdt*ijqTytvF+ zxX9ff%j4RdB9p+Sg+V1cUVtKD+*oZt4B#wsR#*2bSXBi*xT#_@4|T$Q{5BA1>y7rDFqo=Flo z;@KPu-Z6~&W)wbuSsJj`@a%EZt$8Uo(A{7E{y^wn*Q2w}EClw>dG#b89l5M1IM|yi zX(|W0-mxpSM2wywX&Hsgp>^O4n__yuQE|#~6)1cbHy@r`+m)IvK||l7hlO>Mg>SEX z(4X*Vi`0j2GPhOPETBZTmd5)3!H07JuULqI`0)pY+kgYQ z9=617uOd3Vl8@XB?$7*vePqAqTh#vUI;Yj-KA#b;ned?+(J-io1zZfY_Uhx=K3*(p z6lfG<{ovOg6z{~*vnL1kj1--VYDPKNTwP-oX!~Az?$$6=;C(1b_Ewj9xUQIAR$b?U zf;t^S6b2o0NL%GSN}>uu1}Hx0CeyRs@b*JwP6Eq{sw3et_WH^*CkfN$)c3Q_Lj3O* zBM_2 z2yX~@9-98sR@jyK4zDC=G^gw3kX67 z;}>FH#xa?04OL9?7rU3&hrKK$P{f`>2wDH;`(*O+=k~jHsrxVm_309E5R0mJ4Xk<$ zFNjUYcxQ@|59{hY$pJS9=H}31LUZ}l%Jl_O^TXf6P*1Wq z3ji`vq=81l+Sk7*8j6_ha(H%b_1mG2H0PtiN82Kn@P*->zEExi1)PX2yOh!G1TeEM z3aTMf*nuj_C|q_bOLS$Vlt0Mvpssx0Um4A3%%w7r^R^U>5($zSil8`10i!1c!wyH? zOBntt8nG^eKoewm&1OpI6;JvR+Lo8Ja?C01pzRm27b~rc3`Z#*>c2)|>YSN$)b~|i z)$ZLPo3n^w6s&u9akXC36AFPF@)qMUf*6^;17}Tc+zB!}=@eUR-HmkIPRw#H@}yiJ zbfh;igHJgY9 z7TLYzI?iG}A8BN1?F~i_$PCyEuEYp8pjJfmJWU*c$~~#Z7(P#sk1#X}XA1&Qak~#P vML>=){9P8qO$4A6R8Xe1y8QpnH{$tO8vMB1hgsi%7zZ-HXnmmu??w6#X`+kW literal 0 HcmV?d00001 diff --git a/acceptance_tests/tests/resources/labels/default_locale/no_default_value/language__expected.png b/acceptance_tests/tests/resources/labels/default_locale/no_default_value/language__expected.png new file mode 100644 index 0000000000000000000000000000000000000000..84cc656581abc71234ec715a66ff25c19afdb691 GIT binary patch literal 670 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!Q#hD_=;qE)s}QC+I22iHjh0cO z%|fTngXGd=l$os~!(5ilC3CE^pUxj~&M%+m{pIt#&-?kj&-><}knZ3O&TGwHk-(YN%(!h4NAW&~fo_J%LUNnJqho@C(|0tI_%zZqsICtt)c@@U<8eeDfCgnco6IP*()`1n-UKM#xNhMm>AAl4&^_ zJ2IXjl<9?Y`|MNKhL(dw7=p}T`MQx7Ok8~Y-q7gOc8yKnvPfPUx=H4lq)N@IR9)E4 zD}Y8N2u7U%CQURDo!c`rqcjkcM}g*B0K`TICN2XYDf*dnJ2Oe$>;89Zs`O=3Ct{j% zVw%qk7<(xmstfI_bV!}~_>?fj%2(`)PMGSa%jNRvp*r&X9hHjMSW{iSwYGs#J z%F?9SWjlz3!rFE)7(9lLc@Bm^!E<%bWTLjV_~}6sNy~ds$W4rjjARA9K>8Or)Ke&w ziergOOG}T}KJL39N?%=?mGs*A~X6Bwv zL>CSm);Db>8L08aq>_7w&~b zs-_-SuTRzk@uzMZ9cZ_tLPK9BZ3boRv~-l;hio;cDtkLS-&4kOaRh>(9{WbQI8EMu zRKB8!su)r0e1d5xIS|^4|6snx^p-5m3gUy&=mkm7whG?*#W-*8{DQiWmd9g0^nF&4 zO$E6a_1!V;TYY0=Bc1Lh=!_G!PIb#Ds8WhgGdtwUMDMIr+HdT!)yRn9eng)T9WCY2 z+*g&#@JD+uSt#W4+}pR~^GN!-85Xu`Y0)}t9M8+JK}!+cao`N+WYb=fBgw+VOr2r!l7CHVUq>BKr$hhvDh&S(b3j61C4j* z8d?Qk5L&5*6VOan_-%W>{!t2QM-4J@jee@s#4+t`B4vJlo~M8dD;!f65a7bXCYw49UP2;~ zYFFr^UpCR^ScV|5*B{@La{{Bn67SOK^c0x!yD#$bcPV1(BI-aCt+Jq*j4Q1!G1?z| zmUDT$WM9yN;AU#xlq2iZKFl-MZEkOC6oK0oX`EHD`r-VOPM2}Z>|X^QA=Z*BG;(kguKh28?)yAG~rhiQ+v2|&toa-CgvXOgJq@7Ud& zjyoUn@xfJV6&@T2H!d~SK;X>xZU7epkpDdSH_dePTImra%0=C(fQK8>mF0Z?+CO*Z Bdw2i< literal 0 HcmV?d00001 diff --git a/acceptance_tests/tests/resources/labels/no_default_locale/default_value/language__expected.png b/acceptance_tests/tests/resources/labels/no_default_locale/default_value/language__expected.png new file mode 100644 index 0000000000000000000000000000000000000000..3272ee1843f3e5a7fe65bfef2308c39fd8e20693 GIT binary patch literal 1916 zcmd^A`#aMM9R7x|GN&d-R=;qE)s}QC+I22iHjh0cO z%|fTngXGd=l$os~!(5ilC3CE^pUxj~&M%+m{pIt#&-?kj&-><}knZ3O&TGwHk-(YN%(!h4NAW&~fo_J%LUNnJqho@C(|0tI_%zZqsICtt)c@@U<8eeDfCgnco6IP*()`1n-UKM#xNhMm>AAl4&^_ zJ2IXjl<9?Y`|MNKhL(dw7=p}T`MQx7Ok8~Y-q7gOc8yKnvPfPUx=H4lq)N@IR9)E4 zD}Y8N2u7U%CQURDo!c`rqcjkcM}g*B0K`TICN2XYDf*dnJ2Oe$>;89Zs`O=3Ct{j% zVw%qk7<(xmstfI_bV!}~_>?fj%2(`)PMGSa%jNRvp*r&X9hHjMSW{iSwYGs#J z%F?9SWjlz3!rFE)7(9lLc@Bm^!E<%bWTLjV_~}6sNy~ds$W4rjjARA9K>8Or)Ke&w ziergOOG}T}KJL39N?%=?mGs*A~X6Bwv zL>CSm);Db>8L08aq>_7w&~b zs-_-SuTRzk@uzMZ9cZ_tLPK9BZ3boRv~-l;hio;cDtkLS-&4kOaRh>(9{WbQI8EMu zRKB8!su)r0e1d5xIS|^4|6snx^p-5m3gUy&=mkm7whG?*#W-*8{DQiWmd9g0^nF&4 zO$E6a_1!V;TYY0=Bc1Lh=!_G!PIb#Ds8WhgGdtwUMDMIr+HdT!)yRn9eng)T9WCY2 z+*g&#@JD+uSt#W4+}pR~^GN!-85Xu`Y0)}t9M8+JK}!+cao`N+WYb=fBgw+VOr2r!l7CHVUq>BKr$hhvDh&S(b3j61C4j* z8d?Qk5L&5*6VOan_-%W>{!t2QM-4J@jee@s#4+t`B4vJlo~M8dD;!f65a7bXCYw49UP2;~ zYFFr^UpCR^ScV|5*B{@La{{Bn67SOK^c0x!yD#$bcPV1(BI-aCt+Jq*j4Q1!G1?z| zmUDT$WM9yN;AU#xlq2iZKFl-MZEkOC6oK0oX`EHD`r-VOPM2}Z>|X^QA=Z*BG;(kguKh28?)yAG~rhiQ+v2|&toa-CgvXOgJq@7Ud& zjyoUn@xfJV6&@T2H!d~SK;X>xZU7epkpDdSH_dePTImra%0=C(fQK8>mF0Z?+CO*Z Bdw2i< literal 0 HcmV?d00001 diff --git a/acceptance_tests/tests/resources/labels/no_default_locale/default_value/language_de_expected.png b/acceptance_tests/tests/resources/labels/no_default_locale/default_value/language_de_expected.png new file mode 100644 index 0000000000000000000000000000000000000000..eb3c686304b3d0e456bdf4fe7ee6e855bab3b54b GIT binary patch literal 2209 zcmeHJ`&W|b7XH*B8B0pjJZZJGOl{OAqeE#oMH?cMP!SacGcTEjG^V0?S9Fe*Q<~Sj zBwi*$E)_!K^@vTjH0+{?iHb2!IfzW&FjK(u{WgEW`RSZ@?e(trvi92hdG_9vzXAZDF=#IjTnb5570eh6GkZy)(<}~C_m?#M zR_3L!tBP_kIs9g6S4ryYjGnwyCmYkK8}8^&qR6_a0qm#L)vP%6RPE1sX|9v%$ERoH zty$}HQ>3`8(X}~6q~=4OzQYHmdqGRm?MA>z10YKPNR2swoB=NGfn?v^<*s)blKs{d zs6^>m+Ztkfg7vHuVZbL}0NSr-b`1|eYYnnf%>ZP=|DM}MyiPU#jH=r>k-GTD)Nzsj zR{INeH3+h_ugGd?89&fRk#&A+@KvWx8$i$#-qf9Z+Clv+n>xx13uFb+`zj6+2YjYA z6a(526J4uoYGqALjf5RC zqhOa7U3hS6vMcS&-ZM92ti!s~(PMVF?v#Z~Z|glGUjD4jfUm3HHKNqBBjfE!aq*Tj zSytAftifT*!uux?`I{|ZS`M*8fP3VZC6wnF7{-bj#eo}<2c5SK{H4>bdzU*^c^lfD zue|FG9SMJ4V4QQPqIYPHVs?!;H{ zhIs@U$O#=|_%@pC`yqXGUPzF2rBQpb^#~IMu-$8`3lS|7PoumaoXQZ-)VN-6Efx&S zYaO8$2SXihO|~ahAEl6jY$0@!&;28hew^-`?kh^$&~>RB*&bX{S9_wOVH1JDHih16 z`s6Zbq}d`DRlywtIZp7k19158Kd^gl8S;n>tPObxCvkb|o}-ViYBj|(utHc>oP=cp zkLBG^&u%idOxLXXUKW5nDOt_uJc0yQ$7+vLZ}v<1ao>|VgUY~IL(1H9AC0XLF*({5 z)i_Actc2Y{4Up6Y--$k&zG2i0)^dEAjt*a2R!B&<16V=6yu8Qa&iXb__1=FcDu$9Z z!%f2tXEw)ep`rbjDx)Ep1=obRkf?#u;+@V=z0T>EcPyL zPIEasoC#)@K{ns^f?6k!3w#WbWE4B+muxR+(hBbe`<`V!CVRU*MqFyr4l=_+yt(mB zukE8$Fe5!=@%80DeVbVv(i|5-mM0!-9r-*jf3A}9;p1*iiYX`=TE^Gwgkv0tpVJH8E~Z%rw8-jx+OZ5R(F%7X!Xj zNf*aXC&8mdZ(k_Dr?HXQ3uTjIMMX zFd=#tY}tvpT1gt!-W2W%9dVBT-JRv68*xVo@oa*EI4NY3>_^nf(H3hRpG}_X?sD7W zdC$8YRYvsLo@_0?#(MVHegR-Xo4ez S1{&CU0O&IqFAnlz?tcJG047lY literal 0 HcmV?d00001 diff --git a/acceptance_tests/tests/resources/labels/no_default_locale/default_value/language_fr_expected.png b/acceptance_tests/tests/resources/labels/no_default_locale/default_value/language_fr_expected.png new file mode 100644 index 0000000000000000000000000000000000000000..3c3b1829fb537deba92616838a58dbfcbfccef69 GIT binary patch literal 2541 zcmeHJ`8(8WAD&?hCd*XljBzXpV=237q+v!S%duyTtVNhGvXsymjXe%wGzQTy5|ivA z981C+l895vGGm*vg%0t4-+$x1-d~>Uxt{B~KlgLppZofJ?i5>VGf|i{3O;2fhpd4XRlQunxBv`DM-zArH-Wi0 znZVt|5{4|nxJsEUzC1}dIgej{)7}KGCis6S=!Jg#-Zt;Fdt*ijqTytvF+ zxX9ff%j4RdB9p+Sg+V1cUVtKD+*oZt4B#wsR#*2bSXBi*xT#_@4|T$Q{5BA1>y7rDFqo=Flo z;@KPu-Z6~&W)wbuSsJj`@a%EZt$8Uo(A{7E{y^wn*Q2w}EClw>dG#b89l5M1IM|yi zX(|W0-mxpSM2wywX&Hsgp>^O4n__yuQE|#~6)1cbHy@r`+m)IvK||l7hlO>Mg>SEX z(4X*Vi`0j2GPhOPETBZTmd5)3!H07JuULqI`0)pY+kgYQ z9=617uOd3Vl8@XB?$7*vePqAqTh#vUI;Yj-KA#b;ned?+(J-io1zZfY_Uhx=K3*(p z6lfG<{ovOg6z{~*vnL1kj1--VYDPKNTwP-oX!~Az?$$6=;C(1b_Ewj9xUQIAR$b?U zf;t^S6b2o0NL%GSN}>uu1}Hx0CeyRs@b*JwP6Eq{sw3et_WH^*CkfN$)c3Q_Lj3O* zBM_2 z2yX~@9-98sR@jyK4zDC=G^gw3kX67 z;}>FH#xa?04OL9?7rU3&hrKK$P{f`>2wDH;`(*O+=k~jHsrxVm_309E5R0mJ4Xk<$ zFNjUYcxQ@|59{hY$pJS9=H}31LUZ}l%Jl_O^TXf6P*1Wq z3ji`vq=81l+Sk7*8j6_ha(H%b_1mG2H0PtiN82Kn@P*->zEExi1)PX2yOh!G1TeEM z3aTMf*nuj_C|q_bOLS$Vlt0Mvpssx0Um4A3%%w7r^R^U>5($zSil8`10i!1c!wyH? zOBntt8nG^eKoewm&1OpI6;JvR+Lo8Ja?C01pzRm27b~rc3`Z#*>c2)|>YSN$)b~|i z)$ZLPo3n^w6s&u9akXC36AFPF@)qMUf*6^;17}Tc+zB!}=@eUR-HmkIPRw#H@}yiJ zbfh;igHJgY9 z7TLYzI?iG}A8BN1?F~i_$PCyEuEYp8pjJfmJWU*c$~~#Z7(P#sk1#X}XA1&Qak~#P vML>=){9P8qO$4A6R8Xe1y8QpnH{$tO8vMB1hgsi%7zZ-HXnmmu??w6#X`+kW literal 0 HcmV?d00001 diff --git a/acceptance_tests/tests/resources/labels/no_default_locale/default_value/language_it_expected.png b/acceptance_tests/tests/resources/labels/no_default_locale/default_value/language_it_expected.png new file mode 100644 index 0000000000000000000000000000000000000000..84cc656581abc71234ec715a66ff25c19afdb691 GIT binary patch literal 670 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!Q#hD_ + + + localized_labels + + localized_labels + + + Localized labels + + + + #000000 + + + + + + + diff --git a/acceptance_tests/tests/resources/localized_no_default.sld b/acceptance_tests/tests/resources/localized_no_default.sld new file mode 100644 index 000000000..bc1d5a8ad --- /dev/null +++ b/acceptance_tests/tests/resources/localized_no_default.sld @@ -0,0 +1,37 @@ + + + + localized_style + + localized_no_default + + + Localized, no default value + + <Localized lang="en">English</Localized> + <Localized lang="de">Deutsch</Localized> + <Localized lang="fr">Français</Localized> + <Localized lang="it">Italiano</Localized> + + + + + circle + + #FF0000 + + + 6 + + + + + + + diff --git a/acceptance_tests/tests/resources/localized_with_default.sld b/acceptance_tests/tests/resources/localized_with_default.sld new file mode 100644 index 000000000..316966c6b --- /dev/null +++ b/acceptance_tests/tests/resources/localized_with_default.sld @@ -0,0 +1,37 @@ + + + + localized_style + + localized_with_default + + + Localized, with default value + + Default label<Localized lang="en">English</Localized> + <Localized lang="de">Deutsch</Localized> + <Localized lang="fr">Français</Localized> + <Localized lang="it">Italiano</Localized> + + + + + circle + + #FF0000 + + + 6 + + + + + + + diff --git a/acceptance_tests/tests/resources/wfs_payload.xml b/acceptance_tests/tests/resources/wfs_payload.xml new file mode 100644 index 000000000..eefd14e03 --- /dev/null +++ b/acceptance_tests/tests/resources/wfs_payload.xml @@ -0,0 +1,16 @@ + + + + + + 2600000.0 1200000.0 + + + 2024-05-13T08:14:48.763Z + 10 + Title + + + \ No newline at end of file diff --git a/acceptance_tests/tests/test_datastore.py b/acceptance_tests/tests/test_datastore.py new file mode 100644 index 000000000..b44d8212c --- /dev/null +++ b/acceptance_tests/tests/test_datastore.py @@ -0,0 +1,24 @@ +from conftest import PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD, PGSCHEMA + + +def test_create_get_and_delete_datastore(geoserver): + workspace = datastore = "test_create_pg_datastore" + geoserver.create_workspace(workspace) + response = geoserver.create_pg_datastore( + workspace=workspace, + datastore=datastore, + pg_host=PGHOST, + pg_port=PGPORT, + pg_db=PGDATABASE, + pg_user=PGUSER, + pg_password=PGPASSWORD, + pg_schema=PGSCHEMA, + set_default_datastore=True, + ) + assert response.status_code == 201 + response = geoserver.get_request( + f"/rest/workspaces/{workspace}/datastores/{datastore}.json" + ) + assert response.status_code == 200 + response = geoserver.delete_workspace(workspace) + assert response.status_code == 200 diff --git a/acceptance_tests/tests/test_gwc.py b/acceptance_tests/tests/test_gwc.py new file mode 100644 index 000000000..d5af14f84 --- /dev/null +++ b/acceptance_tests/tests/test_gwc.py @@ -0,0 +1,47 @@ +import pytest + +WORKSPACE = "test_gwc" +WMTS_STORE = "test_gwc_store" +WMTS_LAYER = "ch.swisstopo.swissimage" + + +@pytest.fixture(scope="module") +def geoserver_with_gwc_layers(geoserver): + geoserver.create_workspace(WORKSPACE) + geoserver.create_wmts_store( + WORKSPACE, + WMTS_STORE, + capabilities="https://wmts.geo.admin.ch/EPSG/4326/1.0.0/WMTSCapabilities.xml", + ) + geoserver.create_wmts_layer(WORKSPACE, WMTS_STORE, WMTS_LAYER) + geoserver.get_request( + f"/rest/workspaces/{WORKSPACE}/wmtsstores/{WMTS_STORE}/layers/{WMTS_LAYER}.json" + ) + response = geoserver.publish_gwc_layer(WORKSPACE, WMTS_LAYER) + assert response.status_code == 200 + yield geoserver + geoserver.delete_workspace(WORKSPACE) + + +def test_tile_cache(geoserver_with_gwc_layers): + + response = geoserver_with_gwc_layers.get_tile( + format="image/png", + layer=f"{WORKSPACE}:{WMTS_LAYER}", + tile_matrix_set="EPSG:4326", + tile_matrix="EPSG:4326:9", + row=122, + column=534, + ) + assert response.info().get("Content-Type") == "image/png" + assert response.info().get("Geowebcache-Cache-Result") == "MISS" + + response = geoserver_with_gwc_layers.get_tile( + format="image/png", + layer=f"{WORKSPACE}:{WMTS_LAYER}", + tile_matrix_set="EPSG:4326", + tile_matrix="EPSG:4326:9", + row=122, + column=534, + ) + assert response.info().get("Geowebcache-Cache-Result") == "HIT" diff --git a/acceptance_tests/tests/test_i18n.py b/acceptance_tests/tests/test_i18n.py new file mode 100755 index 000000000..81640659e --- /dev/null +++ b/acceptance_tests/tests/test_i18n.py @@ -0,0 +1,417 @@ +#!/bin/env python + +import pytest +from conftest import RESOURCE_DIR, WORKSPACE +from lib.utils import compare_images, write_actual_image +from requests.exceptions import JSONDecodeError +from sqlalchemy.sql import text + + +def international_title(default=True, de=True, fr=True, it=True, rm=True): + title = {} + if default: + title["default"] = "Default title" + if de: + title["de"] = "Punkte" + if fr: + title["fr"] = "Points" + if it: + title["it"] = "Punti" + if rm: + title["rm"] = "Puncts" + return title + + +def assert_legend(geoserver, style, language, expected_label): + response = geoserver.get_legend_graphic( + "i18n_legend", + format="application/json", + language=language, + style=style, + workspace=WORKSPACE, + ) + try: + label = response.json()["Legend"][0]["rules"][0]["title"] + assert label == expected_label + except (KeyError, JSONDecodeError): + print(f"Invalid response for language '{language}:'\n{response.content}") + assert False + + +@pytest.fixture(scope="module") +def geoserver_with_i18n_layers(geoserver): + + # Create feature type with all languages + layer1 = "layer_all_languages" + title1 = international_title(default=True, de=True, fr=True, it=True, rm=True) + geoserver.create_feature_type(layer1, title=title1, epsg=2056) + + # Create feature type without Rumantsch + layer2 = "layer_no_rumantsch" + title2 = international_title(default=True, de=True, fr=True, it=True, rm=False) + geoserver.create_feature_type(layer2, title=title2, epsg=2056) + + # Create feature type without default language nor Rumantsch + layer3 = "layer_no_default_no_rumantsch" + title3 = international_title(default=False, de=True, fr=True, it=True, rm=False) + geoserver.create_feature_type(layer3, title=title3, epsg=2056) + + yield geoserver + + +@pytest.fixture(scope="module") +def geoserver_default_locale_it(geoserver_with_i18n_layers): + geoserver_with_i18n_layers.set_default_locale_for_service(WORKSPACE, "it") + yield geoserver_with_i18n_layers + geoserver_with_i18n_layers.unset_default_locale_for_service(WORKSPACE) + + +@pytest.fixture(scope="module") +def geoserver_i18n_legend_layer(geoserver): + geoserver.create_workspace(WORKSPACE, set_default_workspace=True) + geoserver.create_feature_type("i18n_legend", epsg=2056) + geoserver.create_style_from_file( + "localized_with_default.sld", + f"{RESOURCE_DIR}/localized_with_default.sld", + workspace=WORKSPACE, + ) + geoserver.create_style_from_file( + "localized_no_default.sld", + f"{RESOURCE_DIR}/localized_no_default.sld", + workspace=WORKSPACE, + ) + yield geoserver + + +@pytest.fixture(scope="function") +def geoserver_i18n_legend_layer_and_default_locale_it(geoserver_i18n_legend_layer): + geoserver_i18n_legend_layer.set_default_locale_for_service(WORKSPACE, "it") + yield geoserver_i18n_legend_layer + geoserver_i18n_legend_layer.unset_default_locale_for_service(WORKSPACE) + + +@pytest.fixture(scope="module") +def geoserver_i18n_label_layer(geoserver, db_session): + feature_type = "i18n_labels" + style = "localized_labels" + file = f"{RESOURCE_DIR}/{style}.sld" + attributes = { + "geom": {"type": "Point", "required": True}, + "label_default": {"type": "string", "required": False}, + "label_de": {"type": "string", "required": False}, + "label_fr": {"type": "string", "required": False}, + } + geoserver.create_feature_type(feature_type, attributes=attributes, epsg=2056) + geoserver.create_style_from_file(style, file, workspace=WORKSPACE) + # Feature with labels in German, French and a default value + db_session.execute( + text( + f"INSERT INTO {feature_type} (geom, label_default, label_de, label_fr) VALUES " + "(public.ST_SetSRID(public.ST_MakePoint(2600000, 1200000), 2056), 'Default label', 'Deutsches Label', 'Étiquette française')" + ) + ) + # Feature with labels in German, French and no default value + db_session.execute( + text( + f"INSERT INTO {feature_type} (geom, label_de, label_fr) VALUES " + "(public.ST_SetSRID(public.ST_MakePoint(2700000, 1300000), 2056), 'Deutsches Label', 'Étiquette française')" + ) + ) + db_session.commit() + yield geoserver + + +@pytest.fixture(scope="module") +def geoserver_i18n_label_default_locale_fr(geoserver_i18n_label_layer): + geoserver_i18n_label_layer.set_default_locale_for_service(WORKSPACE, "fr") + yield geoserver_i18n_label_layer + geoserver_i18n_label_layer.unset_default_locale_for_service(WORKSPACE) + + +@pytest.mark.parametrize( + "language,expected_titles", + [ + ( + "de", + { + "layer_all_languages": "Punkte", + "layer_no_rumantsch": "Punkte", + "layer_no_default_no_rumantsch": "Punkte", + }, + ), + ( + "de,fr", + { + "layer_all_languages": "Punkte", + "layer_no_rumantsch": "Punkte", + "layer_no_default_no_rumantsch": "Punkte", + }, + ), + ( + "fr,de", + { + "layer_all_languages": "Points", + "layer_no_rumantsch": "Points", + "layer_no_default_no_rumantsch": "Points", + }, + ), + ( + "rm", + { + "layer_all_languages": "Puncts", + "layer_no_rumantsch": "Default title", + "layer_no_default_no_rumantsch": "DID NOT FIND i18n CONTENT FOR THIS ELEMENT", + }, + ), + ( + "en", + {}, + ), + ( + None, + { + "layer_all_languages": "Default title", + "layer_no_rumantsch": "Default title", + "layer_no_default_no_rumantsch": "Punkte", + }, + ), + ( + "foobar", + {}, + ), + ], +) +def test_i18n_layers(geoserver_with_i18n_layers, language, expected_titles): + capabilities = geoserver_with_i18n_layers.get_wms_layers(WORKSPACE, language) + layers = capabilities.get("Layer") + if type(layers) is list: + for expected_layer, expected_title in expected_titles.items(): + actual_layer = next( + (layer for layer in layers if layer["Name"] == expected_layer), {} + ) + assert actual_layer.get("Title") == expected_title + else: + print(capabilities) + assert expected_titles == {} + assert "ServiceExceptionReport" in capabilities + + +@pytest.mark.parametrize( + "language,expected_titles", + [ + ( + "de", + { + "layer_all_languages": "Punkte", + "layer_no_rumantsch": "Punkte", + "layer_no_default_no_rumantsch": "Punkte", + }, + ), + ( + "rm", + { + "layer_all_languages": "Puncts", + "layer_no_rumantsch": "Default title", + "layer_no_default_no_rumantsch": "DID NOT FIND i18n CONTENT FOR THIS ELEMENT", + }, + ), + ( + "en", + {}, + ), + ( + None, + { + "layer_all_languages": "Punti", + "layer_no_rumantsch": "Punti", + "layer_no_default_no_rumantsch": "Punti", + }, + ), + ], +) +@pytest.mark.skip(reason="Default locale is ignored in gs-cloud 1.6.1") +def test_i18n_layers_default_locale( + geoserver_default_locale_it, language, expected_titles +): + layers = geoserver_default_locale_it.get_wms_layers(WORKSPACE, language) + if type(layers) is list: + for expected_layer, expected_title in expected_titles.items(): + actual_layer = next( + (layer for layer in layers if layer["Name"] == expected_layer), {} + ) + print(actual_layer["Name"]) + assert actual_layer.get("Title") == expected_title + else: + print(layers) + assert expected_titles == {} + assert "ServiceExceptionReport" in layers + + +@pytest.mark.parametrize( + "language,expected_label", + [ + ("en", "English"), + ("de", "Deutsch"), + ("fr", "Français"), + ("it", "Italiano"), + ("rm", "Default label"), + (None, "Default label"), + ("ru", "Default label"), + ("foobar", "Default label"), + ("it,fr,de", "Default label"), + ], +) +def test_i18n_legend_with_default_value( + geoserver_i18n_legend_layer, language, expected_label +): + assert_legend( + geoserver_i18n_legend_layer, + "localized_with_default", + language, + expected_label, + ) + + +@pytest.mark.parametrize( + "language,expected_label", + [ + ("it", "Italiano"), + ("rm", ""), + (None, ""), + ("ru", ""), + ("foobar", ""), + ("it,fr,de", ""), + ], +) +def test_i18n_legend_no_default_value( + geoserver_i18n_legend_layer, language, expected_label +): + + assert_legend( + geoserver_i18n_legend_layer, + "localized_no_default", + language, + expected_label, + ) + + +@pytest.mark.parametrize( + "language,expected_label", + [ + ("en", "English"), + ("de", "Deutsch"), + ("fr", "Français"), + ("it", "Italiano"), + ("rm", "Default label"), + (None, "Default label"), + ("ru", "Default label"), + ("foobar", "Default label"), + ("it,fr,de", "Default label"), + ], +) +def test_i18n_legend_with_default_value_and_default_locale( + geoserver_i18n_legend_layer_and_default_locale_it, language, expected_label +): + assert_legend( + geoserver_i18n_legend_layer_and_default_locale_it, + "localized_with_default", + language, + expected_label, + ) + + +@pytest.mark.parametrize( + "language,expected_label", + [ + ("it", "Italiano"), + ("rm", ""), + (None, ""), + ("ru", ""), + ("foobar", ""), + ("it,fr,de", ""), + ], +) +def test_i18n_legend_no_default_value_default_locale( + geoserver_i18n_legend_layer_and_default_locale_it, language, expected_label +): + + assert_legend( + geoserver_i18n_legend_layer_and_default_locale_it, + "localized_no_default", + language, + expected_label, + ) + + +@pytest.mark.parametrize("language", ["de", "fr", "it", None, ""]) +def test_i18n_labels(geoserver_i18n_label_layer, language): + + response = geoserver_i18n_label_layer.get_map( + layers=["i18n_labels"], + bbox=(2599999.5, 1199999.5, 2600000.5, 1200000.5), + size=(300, 100), + format="image/png", + transparent=False, + styles=["localized_labels"], + language=language, + ) + + file_root = f"labels/no_default_locale/default_value/language_{language}" + write_actual_image(response, file_root) + compare_images(RESOURCE_DIR, file_root) + + +@pytest.mark.parametrize("language", ["it", "", None]) +def test_i18n_labels_no_default_value(geoserver_i18n_label_layer, language): + + response = geoserver_i18n_label_layer.get_map( + layers=["i18n_labels"], + bbox=(2699999.5, 1299999.5, 2700000.5, 1300000.5), + size=(300, 100), + format="image/png", + transparent=False, + styles=["localized_labels"], + language=language, + ) + + file_root = f"labels/no_default_locale/no_default_value/language_{language}" + write_actual_image(response, file_root) + compare_images(RESOURCE_DIR, file_root) + + +@pytest.mark.parametrize("language", ["de", "fr", "it", None, ""]) +def test_i18n_labels_default_locale(geoserver_i18n_label_default_locale_fr, language): + + response = geoserver_i18n_label_default_locale_fr.get_map( + layers=["i18n_labels"], + bbox=(2599999.5, 1199999.5, 2600000.5, 1200000.5), + size=(300, 100), + format="image/png", + transparent=False, + styles=["localized_labels"], + language=language, + ) + + file_root = f"labels/default_locale/default_value/language_{language}" + write_actual_image(response, file_root) + compare_images(RESOURCE_DIR, file_root) + + +@pytest.mark.parametrize("language", ["it", "", None]) +def test_i18n_labels_no_default_value_default_locale( + geoserver_i18n_label_default_locale_fr, language +): + + response = geoserver_i18n_label_default_locale_fr.get_map( + layers=["i18n_labels"], + bbox=(2699999.5, 1299999.5, 2700000.5, 1300000.5), + size=(300, 100), + format="image/png", + transparent=False, + styles=["localized_labels"], + language=language, + ) + + file_root = f"labels/default_locale/no_default_value/language_{language}" + write_actual_image(response, file_root) + compare_images(RESOURCE_DIR, file_root) diff --git a/acceptance_tests/tests/test_wfs.py b/acceptance_tests/tests/test_wfs.py new file mode 100644 index 000000000..fddc26e93 --- /dev/null +++ b/acceptance_tests/tests/test_wfs.py @@ -0,0 +1,76 @@ +from conftest import ( + PGDATABASE, + PGHOST, + PGPASSWORD, + PGPORT, + PGSCHEMA, + PGUSER, + RESOURCE_DIR, +) + + +def test_wfs(geoserver): + workspace = datastore = feature_type = "test_wfs" + attributes = { + "geom": { + "type": "Point", + "required": True, + }, + "id": { + "type": "integer", + "required": True, + }, + "title": { + "type": "string", + "required": False, + }, + "timestamp": { + "type": "datetime", + "required": False, + }, + } + response = geoserver.create_workspace(workspace, set_default_workspace=True) + assert response.status_code == 201 + response = geoserver.create_pg_datastore( + workspace=workspace, + datastore=datastore, + pg_host=PGHOST, + pg_port=PGPORT, + pg_db=PGDATABASE, + pg_user=PGUSER, + pg_password=PGPASSWORD, + pg_schema=PGSCHEMA, + set_default_datastore=True, + ) + assert response.status_code == 201 + response = geoserver.create_feature_type( + feature_type, attributes=attributes, epsg=2056 + ) + assert response.status_code == 201 + + # Post a feature through a WFS request + with open(f"{RESOURCE_DIR}/wfs_payload.xml") as file: + data = file.read() + response = geoserver.post_request(f"/{workspace}/wfs/", data=data) + assert response.status_code == 200 + + # GetFeature request + feature_collection = geoserver.get_feature(workspace, feature_type) + assert type(feature_collection) is dict + assert type(feature_collection.get("features")) is list + feature = feature_collection["features"][0] + properties = feature.get("properties") + assert properties.get("id") == 10 + assert properties.get("title") == "Title" + assert properties.get("timestamp") == "2024-05-13T08:14:48.763Z" + assert feature.get("geometry", {}) == { + "type": "Point", + "coordinates": [2600000, 1200000], + } + assert feature_collection.get("crs") == { + "type": "name", + "properties": {"name": "urn:ogc:def:crs:EPSG::2056"}, + } + + response = geoserver.delete_workspace(workspace) + assert response.status_code == 200 diff --git a/acceptance_tests/tests/test_wms.py b/acceptance_tests/tests/test_wms.py new file mode 100644 index 000000000..73af7b43f --- /dev/null +++ b/acceptance_tests/tests/test_wms.py @@ -0,0 +1,128 @@ +import json + +from conftest import ( + PGDATABASE, + PGHOST, + PGPASSWORD, + PGPORT, + PGSCHEMA, + PGUSER, + RESOURCE_DIR, +) +from lib.utils import compare_images, write_actual_image +from sqlalchemy.sql import text + + +def test_create_and_feature_type_and_get_map(db_session, geoserver): + workspace = datastore = feature_type = "test_create_feature_type" + geoserver.create_workspace(workspace, set_default_workspace=True) + geoserver.create_pg_datastore( + workspace=workspace, + datastore=datastore, + pg_host=PGHOST, + pg_port=PGPORT, + pg_db=PGDATABASE, + pg_user=PGUSER, + pg_password=PGPASSWORD, + pg_schema=PGSCHEMA, + set_default_datastore=True, + ) + response = geoserver.create_feature_type( + feature_type, + epsg=2056, + ) + assert response.status_code == 201 + + # Create feature + db_session.execute( + text( + f"INSERT INTO {feature_type} (geom) VALUES (public.ST_SetSRID(public.ST_MakePoint(2600000, 1200000), 2056))" + ) + ) + db_session.commit() + + # GetMap request + response = geoserver.get_map( + layers=[feature_type], + bbox=(2599999.5, 1199999.5, 2600000.5, 1200000.5), + size=(40, 40), + format="image/png", + transparent=False, + ) + + file_root = f"getmap" + write_actual_image(response, file_root) + compare_images(RESOURCE_DIR, file_root) + + geoserver.delete_workspace(workspace) + + +def test_get_feature_info(db_session, geoserver): + workspace = datastore = feature_type = "test_get_feature_info" + attributes = { + "geom": { + "type": "Point", + "required": True, + }, + "label": { + "type": "string", + "required": False, + }, + } + response = geoserver.create_workspace(workspace, set_default_workspace=True) + assert response.status_code == 201 + response = geoserver.create_pg_datastore( + workspace=workspace, + datastore=datastore, + pg_host=PGHOST, + pg_port=PGPORT, + pg_db=PGDATABASE, + pg_user=PGUSER, + pg_password=PGPASSWORD, + pg_schema=PGSCHEMA, + set_default_datastore=True, + ) + assert response.status_code == 201 + response = geoserver.create_feature_type( + feature_type, attributes=attributes, epsg=2056 + ) + assert response.status_code == 201 + + # Create feature + db_session.execute( + text( + f"INSERT INTO {feature_type} (geom, label) VALUES " + "(public.ST_SetSRID(public.ST_MakePoint(2600000, 1200000), 2056), 'Label')" + ) + ) + db_session.commit() + + # Test that layer is published + response = geoserver.get_request(f"/rest/layers/{workspace}:{feature_type}.json") + assert response.status_code == 200 + + # GetFeatureInfo request + response = geoserver.get_feature_info( + layers=[feature_type], + bbox=(2599999.5, 1199999.5, 2600000.5, 1200000.5), + size=(40, 40), + info_format="application/json", + xy=(20, 20), + ) + + data = json.loads(response.read().decode("utf-8")) + + feature = data.get("features", [])[0] + assert feature + assert feature.get("properties").get("label") == "Label" + assert feature.get("geometry") == { + "type": "Point", + "coordinates": [2600000, 1200000], + } + assert data.get("crs") == { + "type": "name", + "properties": {"name": "urn:ogc:def:crs:EPSG::2056"}, + } + + response = geoserver.delete_workspace(workspace) + assert response.status_code == 200 diff --git a/compose/acceptance_pg_entrypoint/001_create_schemas.sql b/compose/acceptance_pg_entrypoint/001_create_schemas.sql index 230f04cfc..4658ff476 100755 --- a/compose/acceptance_pg_entrypoint/001_create_schemas.sql +++ b/compose/acceptance_pg_entrypoint/001_create_schemas.sql @@ -1,3 +1,4 @@ \c geodata CREATE SCHEMA IF NOT EXISTS test1; -CREATE SCHEMA IF NOT EXISTS test2; \ No newline at end of file +CREATE SCHEMA IF NOT EXISTS test2; +CREATE EXTENSION IF NOT EXISTS postgis; From 617b59f93c111178ae04b814d9287d2c63336ae4 Mon Sep 17 00:00:00 2001 From: Andrea Borghi Date: Tue, 1 Oct 2024 15:29:33 +0200 Subject: [PATCH 4/5] Add entrypoint for acceptance --- Makefile | 3 +- acceptance_tests/Dockerfile | 7 ++++ acceptance_tests/entrypoint.py | 72 ++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100755 acceptance_tests/entrypoint.py diff --git a/Makefile b/Makefile index cc754aa04..df51ac21b 100644 --- a/Makefile +++ b/Makefile @@ -92,8 +92,7 @@ build-acceptance: acceptance-tests: acceptance-tests: build-acceptance (cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) up -d) - sleep 30 - (cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) exec -T acceptance pytest . -vvv --color=yes) + (cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) exec -T acceptance bash -c 'until [ -f /tmp/healthcheck ]; do echo "Waiting for /tmp/healthcheck to be available..."; sleep 5; done && pytest . -vvv --color=yes') .PHONY: stop-acceptance-tests stop-acceptance-tests: build-acceptance diff --git a/acceptance_tests/Dockerfile b/acceptance_tests/Dockerfile index 0eead3f8b..d48591306 100644 --- a/acceptance_tests/Dockerfile +++ b/acceptance_tests/Dockerfile @@ -12,4 +12,11 @@ COPY . /acceptance_tests WORKDIR /acceptance_tests RUN python3 -m pip install --disable-pip-version-check . +COPY entrypoint.py /bin/entrypoint.py + +ENV PYTHONUNBUFFERED=1 +ENTRYPOINT ["/bin/entrypoint.py"] + +HEALTHCHECK --interval=5s --start-period=15s --retries=20 CMD test -f /tmp/healthcheck || exit 1 + CMD ["sleep", "infinity"] diff --git a/acceptance_tests/entrypoint.py b/acceptance_tests/entrypoint.py new file mode 100755 index 000000000..033418b38 --- /dev/null +++ b/acceptance_tests/entrypoint.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +import time +import os +import requests +import sys + +# Set variables +start_time = time.time() + +# Set a maximum timeout from the environment or use 60 seconds as default +max_time = int(os.getenv('MAX_TIMEOUT', 60)) + +# Set the GEOSERVER_URL from the environment or use the default value +GEOSERVER_URL = os.getenv('GEOSERVER_URL', 'http://gateway:8080/geoserver/cloud') +GEOSERVER_USERNAME = os.getenv('GEOSERVER_USERNAME', 'admin') +GEOSERVER_PASSWORD = os.getenv('GEOSERVER_PASSWORD', 'geoserver') + +# is we want to start directly with the passed command +IGNORE_HEALTH_CHECK = os.getenv('IGNORE_HEALTH_CHECK', False) + +# Timeout function +def timeout(): + current_time = time.time() + if current_time - start_time > max_time: + return True + return False + +# Array of endpoints to check +endpoints = [ + f"{GEOSERVER_URL}/wms?SERVICE=WMS&REQUEST=GetCapabilities", + f"{GEOSERVER_URL}/wfs?SERVICE=WFS&REQUEST=GetCapabilities", + f"{GEOSERVER_URL}/wps?SERVICE=WPS&REQUEST=GetCapabilities", + f"{GEOSERVER_URL}/wcs?SERVICE=WCS&REQUEST=GetCapabilities", + f"{GEOSERVER_URL}/ows?SERVICE=WMS&REQUEST=GetCapabilities", + f"{GEOSERVER_URL}/gwc", + f"{GEOSERVER_URL}/rest", +] + +if not IGNORE_HEALTH_CHECK: + # Loop through each endpoint and check if it's available + for endpoint in endpoints: + print(f"Waiting for {endpoint} to be available...") + if timeout(): + print("Timeout") + break + + while True: + try: + # Make a request to the endpoint + response = requests.get(endpoint, auth=(GEOSERVER_USERNAME, GEOSERVER_PASSWORD)) + if response.status_code == 200: + print(f"{endpoint} is up") + break + else: + print(f"{endpoint} returned status code {response.status_code}") + except requests.exceptions.RequestException as e: + print(f"{endpoint} is not available - retrying...") + + if timeout(): + print("Timeout reached") + break + + time.sleep(1) + +# create /tmp/healthcheck file to signal that the healthcheck is done +with open("/tmp/healthcheck", "w") as f: + f.write("done") +# Execute the command passed to the script anyway, this is useful for +# running the tests and see what breaks +if len(sys.argv) > 1: + command = sys.argv[1:] + os.execvp(command[0], command) From 7b8356383aa930069e264d869ecabdcfa72dd074 Mon Sep 17 00:00:00 2001 From: Andrea Borghi Date: Tue, 1 Oct 2024 16:10:18 +0200 Subject: [PATCH 5/5] Enable normal build workflows again --- .github/workflows/build.yaml | 46 ++++++++++------------------- .github/workflows/pull-request.yaml | 28 ++++++++++++++++-- Makefile | 22 ++++++++++---- compose/acceptance.yml | 2 -- 4 files changed, 59 insertions(+), 39 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e387d320e..6bf5d5013 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -23,39 +23,25 @@ jobs: uses: actions/checkout@v2 with: submodules: recursive - # - name: Setup Java - # uses: actions/setup-java@v2 - # with: - # distribution: 'temurin' - # java-version: '21' - # cache: 'maven' - - name: Install CI dependencies - run: python3 -m pip install --user --requirement=ci/requirements.txt - - # - name: Validate source code formatting - # run: make lint - - # - name: Build and test - # run: | - # make install test + - name: Setup Java + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: '21' + cache: 'maven' - # - name: Build docker images - # run: | - # make build-image + - name: Validate source code formatting + run: make lint - - name: Run acceptance tests + - name: Build and test run: | - make acceptance-tests + make install test - - name: Print docker compose logs - run: (cd compose && c2cciutils-docker-logs) - if: always() - - - name: Cleanup acceptance tests + - name: Build docker images run: | - make stop-acceptance-tests + make build-image - # - name: Remove project jars from cached repository - # run: | - # rm -rf ~/.m2/repository/org/geoserver - # find ~/.m2/repository -name "*SNAPSHOT*" -type d | xargs rm -rf {} + - name: Remove project jars from cached repository + run: | + rm -rf ~/.m2/repository/org/geoserver + find ~/.m2/repository -name "*SNAPSHOT*" -type d | xargs rm -rf {} diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 58d5b3071..d82bb7058 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -47,9 +47,33 @@ jobs: run: | make build-image - - name: Run acceptance tests + - name: Install CI dependencies + run: python3 -m pip install --user --requirement=ci/requirements.txt + + - name: Run acceptance tests datadir + run: | + make acceptance-tests-datadir + + - name: Print docker compose logs datadir + run: (cd compose && c2cciutils-docker-logs) + if: always() + + - name: Cleanup acceptance tests datadir run: | - make acceptance-tests + make clean-acceptance-tests-datadir + + # FIXME: fix pgconfig discrepancies before reactivating + # - name: Run acceptance tests pgconfig + # run: | + # make acceptance-tests-pgconfig + + # - name: Print docker compose logs pgconfig + # run: (cd compose && c2cciutils-docker-logs) + # if: always() + + # - name: Cleanup acceptance tests pgconfig + # run: | + # make clean-acceptance-tests-pgconfig - name: Remove project jars from cached repository run: | diff --git a/Makefile b/Makefile index df51ac21b..f23a4f28e 100644 --- a/Makefile +++ b/Makefile @@ -88,12 +88,24 @@ verify-image: build-acceptance: docker build --tag=acceptance:$(TAG) acceptance_tests -.PHONY: acceptance-tests -acceptance-tests: -acceptance-tests: build-acceptance +.PHONY: acceptance-tests-pgconfig +acceptance-tests-pgconfig: +acceptance-tests-pgconfig: build-acceptance + (cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_PGCONFIG_OPTIONS) up -d) + (cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_PGCONFIG_OPTIONS) exec -T acceptance bash -c 'until [ -f /tmp/healthcheck ]; do echo "Waiting for /tmp/healthcheck to be available..."; sleep 5; done && pytest . -vvv --color=yes') + +.PHONY: clean-acceptance-tests-pgconfig +clean-acceptance-tests-pgconfig: + (cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_PGCONFIG_OPTIONS) down -v) + +.PHONY: acceptance-tests-datadir +acceptance-tests-datadir: +acceptance-tests-datadir: build-acceptance (cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) up -d) (cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) exec -T acceptance bash -c 'until [ -f /tmp/healthcheck ]; do echo "Waiting for /tmp/healthcheck to be available..."; sleep 5; done && pytest . -vvv --color=yes') -.PHONY: stop-acceptance-tests -stop-acceptance-tests: build-acceptance +.PHONY: clean-acceptance-tests-datadir +clean-acceptance-tests-datadir: (cd compose/ && TAG=$(TAG) GS_USER=$(UID):$(GID) docker compose $(COMPOSE_ACCEPTANCE_DATADIR_OPTIONS) down -v) + rm -rf compose/catalog-datadir/* + touch compose/catalog-datadir/.keep diff --git a/compose/acceptance.yml b/compose/acceptance.yml index e6d35fa98..f1244be38 100644 --- a/compose/acceptance.yml +++ b/compose/acceptance.yml @@ -20,8 +20,6 @@ services: depends_on: geodatabase: condition: service_healthy - init-datadir: - condition: service_completed_successfully gateway: condition: service_healthy discovery: