Skip to content

Implement testing for server package - #627

Open
hpoeche wants to merge 62 commits into
eclipse-basyx:developfrom
rwth-iat:feat/server-tests
Open

hpoeche wants to merge 62 commits into
eclipse-basyx:developfrom
rwth-iat:feat/server-tests

Conversation

@hpoeche

@hpoeche hpoeche commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Changes

This PR adds unit and integration test to the previously untested server package. In detail the following test setup is used

Unit Testing

Unit tests are primarily written against the served API, employing werkzeug.test.Client to test the werkzeug based API interfaces.

All three server interfaces (repository, discovery, registry) are tested this way.

Format specific testing

All implemented endpoints are tested with all supported Content-Types and for all possible responses.
Responses are not deserialized using the SDK adapter, because round-trip serialization-deserialization is already tested in the SDK. However, the correctness of responses are still verified by parsing them using standard libraries (jsonand lxml.etree) and checking for specific expected data.
To reduce repetition of test for both Content-Types application/json and application/xml, test/interfaces/format_utils.py defines a format-agnostic wrapper FormatClient of werkzeug.test.Client that has subclasses JsonFormatClient and XmlFormatClient handling serialization of request bodies as well as parsing and checking as described above.
The test cases are written against this format-agnostic FormatClient. Function decorators with_json_client/with_xml_client are used to create separate test cases with the correct client at runtime.

Coverage

In general, endpoints are tested for successful response and all error codes, defined in the specification. More specifically:

  • GET endpoints are tested for
    • 200: Check if only requested elements are returned
    • 404: Fail for non-existing elements
    • If pagination is supported: Check repeated paginated requests produce complete result set
  • POST endpoints are tested for
    • 201: Check, if object is stored to the used *ObjectStore
    • 400: Check, if malformed objects are rejected
    • 409: Check, if already existing object are rejected
  • PUT endpoints are tested for
    • 204: Check, if object in the *ObjectStore is altered correctly
    • 404: Fail for non-existing element
  • DELETE endpoints are tested for
    • 204: Check correct deletion in the *ObjectStore
    • 404: Fail for non-existing elements

Additional Tests

While the test above test if the endpoints behavior is compliant to the specification, some additional tests directly test the code in base.py:

  • The pagination logic is centrally defined in base.py, therefore we extensively test it separate for correct logic (pages build a partition of complete set) and parameter handling.
  • As the test above do not deserialize the result, the correct serialization of JsonResponse and XmlResponse is tested separately
  • Server side deserialization (HTTPApiDecoder), is already tested in the test cases above.

Entrypoint Testing

The entrypoints run_registry.py and run_discovery.py (from server/app/services) that start up the respective server profile, configure the storage and the API base path via environment variables. We test that setting these variables results in the correct storage setup inside the tests in server/test/services. We need to use importlib to run the code because the entrypoints are written as scripts, that run their code at import time. Therefore no coverage data can be collected.

Integration Testing

To ensure correct setup of uWSGI, nginx and supervisor inside the docker container, we run basic integration tests.

Therefore, server/test/docker_integration/ has one test module per profile (repository, registry, discovery) that runs against a real, already-running server, checking /description and a full create/retrieve/delete roundtrip per profile.

By default these tests skip if no server is reachable (like test_couchdb.py in the SDK). Setting REQUIRE_SERVER_INTEGRATION_TESTS makes them fail instead of skip. We use this setting in the server-docker CI job, which now builds and tests all three profiles.

hpoeche and others added 30 commits August 28, 2026 18:30
This is a draft to test unittest implementations for the server.
Implement unittests using `werkzeug.test.Client` to test the `/shells`
endpoint, as example. Each endpoint and method is tested for success
and possible failures.

The object store is reset prior to every test case. Tests are repeated
for `application/json` and `application/xml` Content-Types. Therfore
test are written against an abstract `FromatClient` that covers
the details of (de-)serialization behind a simple API for requesting
and parsing. Therefore the base class defining the test cases
(`_ShellsEndpointTest`) is disabled for testing. Two subclasses are
derived from this class, one for each format, that define the correct
`FormatClient` and execute the tests.
…ation thumbnail endpoint."

This reverts commit 496190f, which held the content of eclipse-basyx#618. This was added for testing purpose only. Merge `develop` into this branch, after the PR was closed to obtain the same result.
For now the `/submodel-elements` paths are excluded
Added test class `TestPagination` to `test_base.py` that ensures
pagination by following `cursor` value correctly assembles all items.

Additionally, all endpoints, that should support pagination are
checked if they do so.
To separate testing of the pagination logic from working endpoints,
the shared function for creatin paginated responses is now tested
directly. The base tests on paginated endpoints remain.
In the first version of the tests for the Discovery API, when
data needed to be added to the DiscoveryStore, this was done
through the `POST /lookup/shells/<aasId>` endpoint.

To decouple endpoint tests from each other, the data insertion is
now done directly via the DiscoveryStore.
Comment thread server/test/interfaces/test_registry.py Outdated
Comment thread server/test/interfaces/repository/helpers.py Outdated
Comment thread server/test/test_config.default.ini
Comment thread server/test/interfaces/test_registry.py Outdated
Comment thread server/test/interfaces/test_discovery.py Outdated
Comment thread CONTRIBUTING.md
@hpoeche
hpoeche marked this pull request as ready for review September 27, 2026 17:20
@hpoeche

hpoeche commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Before we merge, we must check if #642 is already resolved and adapt our changes if so.

@s-heppner s-heppner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can someone please also fix the merge conflicts?

Comment on lines +232 to +235
def assert_error(self, response: TestResponse, status_code: int) -> None:
self.assertEqual(status_code, response.status_code, msg=response.get_data(as_text=True))
self.assertIn("success", response.get_data(as_text=True), msg=response.get_data(as_text=True))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if there's a string like "This action was unsuccessful." somewhere in the response body? I don't doubt that this isn't the case rigt now, but this check seems weak and might falsely state success when there was none.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for checking. assert_error() now parses the actual success field instead of doing a substring check on the text.

Comment thread server/test/test_config.default.ini Outdated
# to that file to override the defaults defined here.

[server]
url = http://localhost:8080/api/v3.1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this a problem to hardcode this, value, when it should use the single point of truth from versions.toml?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well spotted, I fixed this in 960c0fb.

paul-gerber-svg and others added 5 commits October 6, 2026 16:13
Drop the per-descriptor `DESCRIPTOR_TYPE_TO_STRING` lookup and nested
helper function; serialize both descriptor lists once and inject the
literal modelType string in two plain loops instead, matching the
pattern already used in `services/support.py`.
Splits the definition of the server `url` value into `host` and
`base_path` in the `test_config.default.ini`. The values are
concatenated in `test_helpers.py` to define the `url` value.
If `base_path` is set to `DEFAULT`, its value is read from `versions.toml`.
This is the default value for `base_path` so the tests do not fail
on API version bumps.
@hpoeche
hpoeche requested a review from s-heppner October 7, 2026 10:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants