geoservercloud 0.8.2.dev2__tar.gz → 0.8.3.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/PKG-INFO +1 -1
  2. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/config.py +2 -0
  3. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/example.config.yaml +4 -0
  4. geoservercloud-0.8.3.dev2/geoserver_acceptance_tests/tests/resources/i18n/labels/default_locale/default_value/language_None_expected.png +0 -0
  5. geoservercloud-0.8.3.dev2/geoserver_acceptance_tests/tests/resources/i18n/labels/default_locale/default_value/language__expected.png +0 -0
  6. geoservercloud-0.8.3.dev2/geoserver_acceptance_tests/tests/resources/i18n/labels/default_locale/default_value/language_de_expected.png +0 -0
  7. geoservercloud-0.8.3.dev2/geoserver_acceptance_tests/tests/resources/i18n/labels/default_locale/default_value/language_fr_expected.png +0 -0
  8. geoservercloud-0.8.3.dev2/geoserver_acceptance_tests/tests/resources/i18n/labels/default_locale/no_default_value/language_None_expected.png +0 -0
  9. geoservercloud-0.8.3.dev2/geoserver_acceptance_tests/tests/resources/i18n/labels/no_default_locale/default_value/language__expected.png +0 -0
  10. geoservercloud-0.8.3.dev2/geoserver_acceptance_tests/tests/resources/i18n/labels/no_default_locale/default_value/language_de_expected.png +0 -0
  11. geoservercloud-0.8.3.dev2/geoserver_acceptance_tests/tests/resources/i18n/labels/no_default_locale/default_value/language_fr_expected.png +0 -0
  12. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/i18n/localized_labels.sld +4 -0
  13. geoservercloud-0.8.3.dev2/geoserver_acceptance_tests/tests/test_installed_fonts.py +20 -0
  14. geoservercloud-0.8.3.dev2/geoserver_acceptance_tests/tests/test_ogcapi_features.py +401 -0
  15. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/services/restclient.py +1 -0
  16. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/pyproject.toml +1 -1
  17. geoservercloud-0.8.2.dev2/geoserver_acceptance_tests/tests/resources/i18n/labels/default_locale/default_value/language_None_expected.png +0 -0
  18. geoservercloud-0.8.2.dev2/geoserver_acceptance_tests/tests/resources/i18n/labels/default_locale/default_value/language__expected.png +0 -0
  19. geoservercloud-0.8.2.dev2/geoserver_acceptance_tests/tests/resources/i18n/labels/default_locale/default_value/language_de_expected.png +0 -0
  20. geoservercloud-0.8.2.dev2/geoserver_acceptance_tests/tests/resources/i18n/labels/default_locale/default_value/language_fr_expected.png +0 -0
  21. geoservercloud-0.8.2.dev2/geoserver_acceptance_tests/tests/resources/i18n/labels/default_locale/no_default_value/language_None_expected.png +0 -0
  22. geoservercloud-0.8.2.dev2/geoserver_acceptance_tests/tests/resources/i18n/labels/no_default_locale/default_value/language__expected.png +0 -0
  23. geoservercloud-0.8.2.dev2/geoserver_acceptance_tests/tests/resources/i18n/labels/no_default_locale/default_value/language_de_expected.png +0 -0
  24. geoservercloud-0.8.2.dev2/geoserver_acceptance_tests/tests/resources/i18n/labels/no_default_locale/default_value/language_fr_expected.png +0 -0
  25. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/LICENSE +0 -0
  26. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/README.md +0 -0
  27. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/.gitignore +0 -0
  28. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/README.md +0 -0
  29. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/__init__.py +0 -0
  30. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/cli.py +0 -0
  31. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/compose/example.compose.yaml +0 -0
  32. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/compose/geodatabase/001_create_schemas.sql +0 -0
  33. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/data/sampledata.tgz +0 -0
  34. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/__init__.py +0 -0
  35. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/conftest.py +0 -0
  36. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/__init__.py +0 -0
  37. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/i18n/__init__.py +0 -0
  38. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/i18n/labels/default_locale/default_value/language_it_expected.png +0 -0
  39. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/i18n/labels/default_locale/no_default_value/language__expected.png +0 -0
  40. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/i18n/labels/default_locale/no_default_value/language_it_expected.png +0 -0
  41. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/i18n/labels/no_default_locale/default_value/language_None_expected.png +0 -0
  42. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/i18n/labels/no_default_locale/default_value/language_it_expected.png +0 -0
  43. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/i18n/labels/no_default_locale/no_default_value/language_None_expected.png +0 -0
  44. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/i18n/labels/no_default_locale/no_default_value/language__expected.png +0 -0
  45. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/i18n/labels/no_default_locale/no_default_value/language_it_expected.png +0 -0
  46. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/i18n/localized_no_default.sld +0 -0
  47. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/i18n/localized_with_default.sld +0 -0
  48. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/wfs/__init__.py +0 -0
  49. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/wfs/wfs_delete_payload.xml +0 -0
  50. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/wfs/wfs_insert_payload.xml +0 -0
  51. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/wms/__init__.py +0 -0
  52. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/resources/wms/getmap_expected.png +0 -0
  53. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/test_cascaded_stores.py +0 -0
  54. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/test_cog.py +0 -0
  55. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/test_datastore.py +0 -0
  56. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/test_get_version.py +0 -0
  57. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/test_gwc.py +0 -0
  58. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/test_i18n.py +0 -0
  59. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/test_imagemosaic.py +0 -0
  60. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/test_imagemosaic_cog.py +0 -0
  61. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/test_wfs.py +0 -0
  62. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/test_wms.py +0 -0
  63. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/test_workspace.py +0 -0
  64. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoserver_acceptance_tests/tests/utils.py +0 -0
  65. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/__init__.py +0 -0
  66. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/geoservercloud.py +0 -0
  67. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/geoservercloudsync.py +0 -0
  68. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/gridsets/2056.xml +0 -0
  69. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/gridsets/21781.xml +0 -0
  70. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/gridsets/3857.xml +0 -0
  71. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/__init__.py +0 -0
  72. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/abstractlayer.py +0 -0
  73. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/common.py +0 -0
  74. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/coverage.py +0 -0
  75. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/coverages.py +0 -0
  76. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/coveragestore.py +0 -0
  77. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/datastore.py +0 -0
  78. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/datastores.py +0 -0
  79. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/featuretype.py +0 -0
  80. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/featuretypes.py +0 -0
  81. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/layer.py +0 -0
  82. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/layergroup.py +0 -0
  83. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/layergroups.py +0 -0
  84. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/resourcedirectory.py +0 -0
  85. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/style.py +0 -0
  86. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/styles.py +0 -0
  87. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/wmslayer.py +0 -0
  88. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/wmssettings.py +0 -0
  89. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/wmsstore.py +0 -0
  90. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/workspace.py +0 -0
  91. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/models/workspaces.py +0 -0
  92. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/services/__init__.py +0 -0
  93. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/services/owsservice.py +0 -0
  94. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/services/restlogger.py +0 -0
  95. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/services/restservice.py +0 -0
  96. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/templates.py +0 -0
  97. {geoservercloud-0.8.2.dev2 → geoservercloud-0.8.3.dev2}/geoservercloud/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: geoservercloud
3
- Version: 0.8.2.dev2
3
+ Version: 0.8.3.dev2
4
4
  Summary: Lightweight Python client to interact with GeoServer Cloud REST API, GeoServer ACL and OGC services
5
5
  License: BSD-2-Clause
6
6
  Author: Camptocamp
@@ -55,6 +55,8 @@ def load_config():
55
55
  if override_pg_schema:
56
56
  config["db"]["pg_schema"] = override_pg_schema
57
57
 
58
+ config["expected_fonts"] = config.get("installed_fonts", [])
59
+
58
60
  # Configure logging for geoservercloud library
59
61
  _setup_logging(config)
60
62
 
@@ -21,3 +21,7 @@ db:
21
21
  pg_user: "geoserver"
22
22
  pg_password: "geoserver"
23
23
  pg_schema: "test1"
24
+
25
+ installed_fonts:
26
+ - "DejaVu Sans"
27
+ - "DejaVu Sans Bold"
@@ -25,6 +25,10 @@
25
25
  <ogc:PropertyName>label_fr</ogc:PropertyName>
26
26
  </ogc:Function>
27
27
  </Label>
28
+ <Font>
29
+ <CssParameter name="font-family">DejaVu Sans</CssParameter>
30
+ <CssParameter name="font-size">10</CssParameter>
31
+ </Font>
28
32
  <Fill>
29
33
  <CssParameter name="fill">#000000</CssParameter>
30
34
  </Fill>
@@ -0,0 +1,20 @@
1
+ import json
2
+
3
+ from geoservercloud import GeoServerCloud
4
+
5
+
6
+ def test_installed_fonts(config: dict, geoserver: GeoServerCloud):
7
+ """Verify that custom fonts are installed in GeoServer."""
8
+
9
+ response = geoserver.rest_service.rest_client.get(
10
+ "/rest/fonts",
11
+ headers={"Accept": "application/json"},
12
+ )
13
+
14
+ assert response.status_code == 200
15
+
16
+ data = json.loads(response.content.decode("utf-8"))
17
+ fonts = data.get("fonts", [])
18
+
19
+ for font in config["expected_fonts"]:
20
+ assert font in fonts, f"Expected font '{font}' not found in installed fonts"
@@ -0,0 +1,401 @@
1
+ """
2
+ OGC API Features acceptance tests for GeoServer.
3
+
4
+ Tests verify compliance with OGC API Features Core specification:
5
+ - Collections endpoint (list all feature types)
6
+ - Single collection metadata
7
+ - Items endpoint (GeoJSON features)
8
+ - Pagination (limit parameter)
9
+ - Spatial filtering (bbox parameter)
10
+ - Queryables endpoint
11
+ - Landing page
12
+ - Conformance classes
13
+ """
14
+
15
+ import json
16
+ from collections.abc import Generator
17
+
18
+ import pytest
19
+ from sqlalchemy import Connection
20
+ from sqlalchemy.sql import text
21
+
22
+ from geoservercloud import GeoServerCloud
23
+
24
+
25
+ @pytest.fixture()
26
+ def ogcapi_workspace() -> str:
27
+ """Fixture to provide a dedicated workspace name for OGC API tests."""
28
+ return "ogcapi_test_workspace"
29
+
30
+
31
+ @pytest.fixture()
32
+ def ogcapi_datastore() -> str:
33
+ """Fixture to provide a dedicated datastore name for OGC API tests."""
34
+ return "ogcapi_test_datastore"
35
+
36
+
37
+ @pytest.fixture()
38
+ def ogcapi_schema() -> str:
39
+ """Fixture to provide a dedicated schema name for OGC API tests."""
40
+ return "test_ogcapi_features"
41
+
42
+
43
+ @pytest.fixture()
44
+ def geoserver_ogcapi_workspace(
45
+ config: dict,
46
+ geoserver: GeoServerCloud,
47
+ ogcapi_workspace: str,
48
+ ogcapi_datastore: str,
49
+ ogcapi_schema: str,
50
+ db_session: Connection,
51
+ ) -> Generator[GeoServerCloud, None, None]:
52
+ """Fixture to provide a dedicated workspace and PG datastore for OGC API tests."""
53
+ db_session.execute(text(f"CREATE SCHEMA IF NOT EXISTS {ogcapi_schema}"))
54
+ db_session.commit()
55
+ geoserver.create_workspace(ogcapi_workspace, set_default_workspace=True)
56
+ geoserver.create_pg_datastore(
57
+ workspace_name=ogcapi_workspace,
58
+ datastore_name=ogcapi_datastore,
59
+ pg_host=config["db"]["pg_host"]["docker"],
60
+ pg_port=config["db"]["pg_port"]["docker"],
61
+ pg_db=config["db"]["pg_db"],
62
+ pg_user=config["db"]["pg_user"],
63
+ pg_password=config["db"]["pg_password"],
64
+ pg_schema=ogcapi_schema,
65
+ set_default_datastore=True,
66
+ )
67
+ yield geoserver
68
+ geoserver.delete_workspace(ogcapi_workspace)
69
+ db_session.execute(text(f"DROP SCHEMA IF EXISTS {ogcapi_schema} CASCADE"))
70
+ db_session.commit()
71
+
72
+
73
+ def test_ogcapi_collections_list(
74
+ ogcapi_workspace: str, geoserver_ogcapi_workspace: GeoServerCloud
75
+ ):
76
+ """
77
+ Test OGC API Features collections endpoint lists all feature types.
78
+
79
+ Endpoint: GET /{workspace}/ogc/features/v1/collections
80
+ """
81
+ # Use the existing workspace and datastore from the geoserver fixture
82
+ feature_type1 = "cities"
83
+ feature_type2 = "roads"
84
+
85
+ # Create two feature types using the existing datastore
86
+ attributes1 = {
87
+ "geom": {"type": "Point", "required": True},
88
+ "name": {"type": "string", "required": True},
89
+ "population": {"type": "integer", "required": False},
90
+ }
91
+ content, status = geoserver_ogcapi_workspace.create_feature_type(
92
+ feature_type1, attributes=attributes1, epsg=2056
93
+ )
94
+ assert status == 201
95
+
96
+ attributes2 = {
97
+ "geom": {"type": "Line", "required": True},
98
+ "name": {"type": "string", "required": True},
99
+ }
100
+ content, status = geoserver_ogcapi_workspace.create_feature_type(
101
+ feature_type2, attributes=attributes2, epsg=2056
102
+ )
103
+ assert status == 201
104
+
105
+ # Test OGC API Features collections endpoint
106
+ response = geoserver_ogcapi_workspace.rest_service.rest_client.get(
107
+ f"/{ogcapi_workspace}/ogc/features/v1/collections?f=application/json"
108
+ )
109
+ assert response.status_code == 200
110
+
111
+ data = json.loads(response.content.decode("utf-8"))
112
+ assert "collections" in data
113
+ collections = data["collections"]
114
+
115
+ # Find our collections
116
+ collection_ids = [c["id"] for c in collections]
117
+ assert feature_type1 in collection_ids
118
+ assert feature_type2 in collection_ids
119
+
120
+ # Verify collection metadata
121
+ cities_collection = next(c for c in collections if c["id"] == feature_type1)
122
+ assert "title" in cities_collection # GeoServer may return "Default title"
123
+ assert "extent" in cities_collection
124
+ assert "spatial" in cities_collection["extent"]
125
+
126
+
127
+ def test_ogcapi_single_collection(
128
+ ogcapi_workspace: str, geoserver_ogcapi_workspace: GeoServerCloud
129
+ ):
130
+ """
131
+ Test OGC API Features single collection metadata endpoint.
132
+
133
+ Endpoint: GET /{workspace}/ogc/features/v1/collections/{collectionId}
134
+ """
135
+ feature_type = "single"
136
+ attributes = {
137
+ "geom": {"type": "Point", "required": True},
138
+ "name": {"type": "string", "required": True},
139
+ }
140
+
141
+ # Create feature type using the existing datastore
142
+ content, status = geoserver_ogcapi_workspace.create_feature_type(
143
+ feature_type, attributes=attributes, epsg=2056
144
+ )
145
+ assert status == 201
146
+
147
+ # Test single collection endpoint
148
+ response = geoserver_ogcapi_workspace.rest_service.rest_client.get(
149
+ f"/{ogcapi_workspace}/ogc/features/v1/collections/{ogcapi_workspace}:{feature_type}?f=application/json"
150
+ )
151
+ assert response.status_code == 200
152
+
153
+ data = json.loads(response.content.decode("utf-8"))
154
+ assert data["id"] == feature_type
155
+ assert "title" in data # GeoServer may return "Default title"
156
+ assert "extent" in data
157
+ assert "links" in data
158
+
159
+ # Verify links include items endpoint
160
+ links = data["links"]
161
+ items_links = [link for link in links if link["rel"] == "items"]
162
+ assert len(items_links) > 0
163
+
164
+
165
+ def test_ogcapi_items_geojson(
166
+ ogcapi_workspace: str,
167
+ db_session: Connection,
168
+ geoserver_ogcapi_workspace: GeoServerCloud,
169
+ ogcapi_schema: str,
170
+ ):
171
+ """
172
+ Test OGC API Features items endpoint returns features as GeoJSON.
173
+
174
+ Endpoint: GET /{workspace}/ogc/features/v1/collections/{collectionId}/items
175
+ """
176
+ feature_type = "items"
177
+ attributes = {
178
+ "geom": {"type": "Point", "required": True},
179
+ "name": {"type": "string", "required": True},
180
+ "category": {"type": "string", "required": False},
181
+ }
182
+
183
+ # Create feature type using the existing datastore
184
+ content, status = geoserver_ogcapi_workspace.create_feature_type(
185
+ feature_type, attributes=attributes, epsg=2056
186
+ )
187
+ assert status == 201
188
+
189
+ # Insert test features (using EPSG:2056 coordinates)
190
+ db_session.execute(
191
+ text(
192
+ f"INSERT INTO {ogcapi_schema}.{feature_type} (geom, name, category) VALUES "
193
+ "(public.ST_SetSRID(public.ST_MakePoint(2600000, 1200000), 2056), 'City A', 'city'), "
194
+ "(public.ST_SetSRID(public.ST_MakePoint(2601000, 1201000), 2056), 'City B', 'city'), "
195
+ "(public.ST_SetSRID(public.ST_MakePoint(2602000, 1202000), 2056), 'City C', 'city')"
196
+ )
197
+ )
198
+ db_session.commit()
199
+
200
+ # Test items endpoint
201
+ response = geoserver_ogcapi_workspace.rest_service.rest_client.get(
202
+ f"/{ogcapi_workspace}/ogc/features/v1/collections/{ogcapi_workspace}:{feature_type}/items?f=application/geo%2Bjson"
203
+ )
204
+ assert response.status_code == 200
205
+
206
+ data = json.loads(response.content.decode("utf-8"))
207
+ assert data["type"] == "FeatureCollection"
208
+ assert "features" in data
209
+ assert len(data["features"]) == 3
210
+
211
+ # Verify feature structure
212
+ feature = data["features"][0]
213
+ assert feature["type"] == "Feature"
214
+ assert "geometry" in feature
215
+ assert feature["geometry"]["type"] == "Point"
216
+ assert "properties" in feature
217
+ assert "name" in feature["properties"]
218
+ assert feature["properties"]["category"] == "city"
219
+
220
+ # Note: CRS is implicit in GeoJSON (defaults to WGS84/EPSG:4326)
221
+ # Modern GeoJSON spec doesn't require explicit CRS field
222
+
223
+
224
+ def test_ogcapi_items_with_limit(
225
+ ogcapi_workspace: str,
226
+ db_session: Connection,
227
+ geoserver_ogcapi_workspace: GeoServerCloud,
228
+ ogcapi_schema: str,
229
+ ):
230
+ """
231
+ Test OGC API Features items endpoint with limit parameter.
232
+
233
+ Endpoint: GET /{workspace}/ogc/features/v1/collections/{collectionId}/items?limit={n}
234
+ """
235
+ feature_type = "pagination_test"
236
+ attributes = {
237
+ "geom": {"type": "Point", "required": True},
238
+ "name": {"type": "string", "required": True},
239
+ }
240
+
241
+ # Create feature type using the existing datastore
242
+ content, status = geoserver_ogcapi_workspace.create_feature_type(
243
+ feature_type, attributes=attributes, epsg=2056
244
+ )
245
+ assert status == 201
246
+
247
+ # Insert 5 features
248
+ for i in range(5):
249
+ db_session.execute(
250
+ text(
251
+ f"INSERT INTO {ogcapi_schema}.{feature_type} (geom, name) VALUES "
252
+ f"(public.ST_SetSRID(public.ST_MakePoint({2600000 + i * 1000}, {1200000 + i * 1000}), 2056), 'Feature {i}')"
253
+ )
254
+ )
255
+ db_session.commit()
256
+
257
+ # Test with limit=2
258
+ response = geoserver_ogcapi_workspace.rest_service.rest_client.get(
259
+ f"/{ogcapi_workspace}/ogc/features/v1/collections/{ogcapi_workspace}:{feature_type}/items?f=application/geo%2Bjson&limit=2"
260
+ )
261
+ assert response.status_code == 200
262
+
263
+ data = json.loads(response.content.decode("utf-8"))
264
+ assert data["type"] == "FeatureCollection"
265
+ assert len(data["features"]) == 2
266
+
267
+ # Verify pagination links exist
268
+ assert "links" in data
269
+ links = data["links"]
270
+ next_links = [link for link in links if link["rel"] == "next"]
271
+ assert len(next_links) > 0, "Should have a 'next' link for pagination"
272
+
273
+
274
+ def test_ogcapi_items_with_bbox(
275
+ ogcapi_workspace: str,
276
+ db_session: Connection,
277
+ geoserver_ogcapi_workspace: GeoServerCloud,
278
+ ogcapi_schema: str,
279
+ ):
280
+ """
281
+ Test OGC API Features items endpoint with bbox filter.
282
+
283
+ Endpoint: GET /{workspace}/ogc/features/v1/collections/{collectionId}/items?bbox={bbox}
284
+ """
285
+ feature_type = "bbox"
286
+ attributes = {
287
+ "geom": {"type": "Point", "required": True},
288
+ "name": {"type": "string", "required": True},
289
+ }
290
+
291
+ # Create feature type using the existing datastore
292
+ content, status = geoserver_ogcapi_workspace.create_feature_type(
293
+ feature_type, attributes=attributes, epsg=2056
294
+ )
295
+ assert status == 201
296
+
297
+ # Insert features in different locations
298
+ db_session.execute(
299
+ text(
300
+ f"INSERT INTO {ogcapi_schema}.{feature_type} (geom, name) VALUES "
301
+ "(public.ST_SetSRID(public.ST_MakePoint(2600000, 1200000), 2056), 'Inside'), "
302
+ "(public.ST_SetSRID(public.ST_MakePoint(2700000, 1300000), 2056), 'Outside')"
303
+ )
304
+ )
305
+ db_session.commit()
306
+
307
+ # Test with bbox that includes only the first point
308
+ # bbox format: minx,miny,maxx,maxy (in WGS84 coordinates)
309
+ # First point is at ~(7.4386, 46.9511), second at ~(8.7745, 47.8428)
310
+ bbox = "7.3,46.8,7.6,47.0" # Only includes first point in WGS84
311
+ response = geoserver_ogcapi_workspace.rest_service.rest_client.get(
312
+ f"/{ogcapi_workspace}/ogc/features/v1/collections/{ogcapi_workspace}:{feature_type}/items"
313
+ f"?f=application/geo%2Bjson&bbox={bbox}"
314
+ )
315
+ assert response.status_code == 200
316
+
317
+ data = json.loads(response.content.decode("utf-8"))
318
+ assert data["type"] == "FeatureCollection"
319
+ assert len(data["features"]) == 1
320
+ assert data["features"][0]["properties"]["name"] == "Inside"
321
+
322
+
323
+ def test_ogcapi_queryables(
324
+ ogcapi_workspace: str, geoserver_ogcapi_workspace: GeoServerCloud
325
+ ):
326
+ """
327
+ Test OGC API Features queryables endpoint.
328
+
329
+ Endpoint: GET /{workspace}/ogc/features/v1/collections/{collectionId}/queryables
330
+ """
331
+ feature_type = "queryables"
332
+ attributes = {
333
+ "geom": {"type": "Point", "required": True},
334
+ "name": {"type": "string", "required": True},
335
+ "age": {"type": "integer", "required": False},
336
+ }
337
+
338
+ # Create feature type using the existing datastore
339
+ content, status = geoserver_ogcapi_workspace.create_feature_type(
340
+ feature_type, attributes=attributes, epsg=2056
341
+ )
342
+ assert status == 201
343
+
344
+ # Test queryables endpoint
345
+ response = geoserver_ogcapi_workspace.rest_service.rest_client.get(
346
+ f"/{ogcapi_workspace}/ogc/features/v1/collections/{ogcapi_workspace}:{feature_type}/queryables"
347
+ # f"?f=application/json"
348
+ )
349
+ assert response.status_code == 200
350
+
351
+ data = json.loads(response.content.decode("utf-8"))
352
+ # Queryables should list the attributes that can be used in filtering
353
+ assert "properties" in data or "queryables" in data
354
+
355
+
356
+ def test_ogcapi_landing_page(ogcapi_workspace: str, geoserver_ogcapi_workspace):
357
+ """
358
+ Test OGC API Features landing page endpoint.
359
+
360
+ Endpoint: GET /{workspace}/ogc/features/v1
361
+ """
362
+
363
+ # Test landing page
364
+ response = geoserver_ogcapi_workspace.rest_service.rest_client.get(
365
+ f"/{ogcapi_workspace}/ogc/features/v1?f=application/json"
366
+ )
367
+ assert response.status_code == 200
368
+
369
+ data = json.loads(response.content.decode("utf-8"))
370
+ assert "links" in data
371
+
372
+ # Verify conformance and collections links exist
373
+ links = data["links"]
374
+ conformance_links = [link for link in links if link["rel"] == "conformance"]
375
+ collections_links = [link for link in links if link["rel"] == "data"]
376
+
377
+ assert len(conformance_links) > 0, "Should have a conformance link"
378
+ assert len(collections_links) > 0, "Should have a collections (data) link"
379
+
380
+
381
+ def test_ogcapi_conformance(ogcapi_workspace: str, geoserver_ogcapi_workspace):
382
+ """
383
+ Test OGC API Features conformance endpoint.
384
+
385
+ Endpoint: GET /{workspace}/ogc/features/v1/conformance
386
+ """
387
+
388
+ # Test conformance endpoint
389
+ response = geoserver_ogcapi_workspace.rest_service.rest_client.get(
390
+ f"/{ogcapi_workspace}/ogc/features/v1/conformance?f=application/json"
391
+ )
392
+ assert response.status_code == 200
393
+
394
+ data = json.loads(response.content.decode("utf-8"))
395
+ assert "conformsTo" in data
396
+
397
+ # Verify it conforms to OGC API Features Core
398
+ conformance_classes = data["conformsTo"]
399
+ assert any(
400
+ "ogcapi-features" in cc or "features/core" in cc for cc in conformance_classes
401
+ )
@@ -31,6 +31,7 @@ class RestClient:
31
31
  headers: dict[str, str] | None = None,
32
32
  ) -> requests.Response:
33
33
  full_url = f"{self.url}{path}"
34
+ gs_logger.debug("Doing GET request to: %s", full_url)
34
35
  response: requests.Response = requests.get(
35
36
  full_url,
36
37
  params=params,
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "geoservercloud"
3
- version = "0.8.2.dev2"
3
+ version = "0.8.3.dev2"
4
4
  description = "Lightweight Python client to interact with GeoServer Cloud REST API, GeoServer ACL and OGC services"
5
5
  authors = ["Camptocamp <info@camptocamp.com>"]
6
6
  license = "BSD-2-Clause"