From 6ab8d2bed5c1a642aa2f24d032920e4963fc28aa Mon Sep 17 00:00:00 2001
From: PGijsbers
Date: Tue, 14 Apr 2026 14:16:26 +0200
Subject: [PATCH 1/7] Better separate different subpages of contributing
guidelines
---
docs/contributing/{contributing.md => setup.md} | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
rename docs/contributing/{contributing.md => setup.md} (99%)
diff --git a/docs/contributing/contributing.md b/docs/contributing/setup.md
similarity index 99%
rename from docs/contributing/contributing.md
rename to docs/contributing/setup.md
index f2e1eb6..63433b1 100644
--- a/docs/contributing/contributing.md
+++ b/docs/contributing/setup.md
@@ -112,7 +112,7 @@ This is useful, for example, to run unit tests in the container:
python -m pytest -x -v -m "not php_api"
```
-## Unit tests
+## Running Unit tests
Our unit tests are written with the [`pytest`](https://pytest.org) framework.
An invocation could look like this:
From 311dc4a7023dc9ac9c1c489ed4313d0b390155f4 Mon Sep 17 00:00:00 2001
From: PGijsbers
Date: Tue, 14 Apr 2026 15:36:29 +0200
Subject: [PATCH 2/7] Update developer documentation
---
docs/contributing/tests.md | 193 ++++++++++++++++++++-----------------
pyproject.toml | 9 +-
2 files changed, 108 insertions(+), 94 deletions(-)
diff --git a/docs/contributing/tests.md b/docs/contributing/tests.md
index 5886d9e..a07f7e5 100644
--- a/docs/contributing/tests.md
+++ b/docs/contributing/tests.md
@@ -1,98 +1,109 @@
-# Writing Tests
+# Testing
-tl;dr:
- - Setting up the `py_api` fixture to test directly against a REST API endpoint is really slow, only use it for migration/integration tests.
- - Getting a database fixture and doing a database call is slow, consider mocking if appropriate.
+This page covers running and writing tests for the REST API.
+It assumes you already followed the instructions on the ["Setup"](setup.md) page.
-## Overhead from Fixtures
-Sometimes, you want to interact with the REST API through the `py_api` fixture,
-or want access to a database with `user_test` or `expdb_test` fixtures.
-Be warned that these come with considerable relative overhead, which adds up when running thousands of tests.
+!!! note "Follow the documentation"
+ Some tests and files in the current test suite need to be updated to reflect these conventions.
+ This documentation specifies the desired way of doing things even if the source code does something else.
+
+## Writing Tests
+
+We use the ubiquitous [Pytest](https://docs.pytest.org) framework when writing tests.
+
+### File Structure
+When writing tests, we have the following additional conventions on the file structure:
+
+ - Use a `_test` suffix when naming our files (not a `test_` prefix)
+ - One dedicated test file per endpoint
+
+
+### General Test Guidelines
+Some guidelines and things to keep in mind when writing tests:
+
+ - Try to keep tests small, so that they fail for one particular reason only.
+ - Mark tests that update the database in anyway with the `mut` marker (`@pytest.mark.mut`).
+ - If the test is excessively slow (>0.1 sec), use a `slow` marker.
+ - The `py_api` and database fixtures have considerable per-test overhead. Use them only when you need them.
+ - When writing assertions the expected value (a constant, or a php response) should be on the right (`assert response == expected`).
+
+### Writing Tests for an Endpoint
+Because the `py_api` and database fixtures provide considerable per-test overhead,
+follow these guidelines for writing a test suite for an endpoint.
+
+Include tests against `py_api` for input validation specific to that endpoint. Validation in reused components should be tested centrally (e.g., Pagination).
```python
-@pytest.mark.parametrize('execution_number', range(5000))
-def test_private_dataset_owner_access(
- execution_number,
- expdb_test: Connection,
- user_test: Connection,
- py_api: TestClient,
-) -> None:
- fetch_user(ApiKey.REGULAR_USER, user_test) # accesses only the user db
- get_estimation_procedures(expdb_test) # accesses only the experiment db
- py_api.get("/does/not/exist") # only queries the api
- pass
+def test_get_dataset_identifier_validation(py_api: httpx.AsyncClient) -> None:
+ response = py_api.get("/datasets/not-an-integer")
+ assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
+```
+
+Include one test against the `py_api` that confirms a successful request has the expected result:
+
+```python
+def test_get_dataset_success(py_api: httpx.AsyncClient) -> None:
+ response = py_api.get("/datasets/1")
+ assert response.status_code == HTTPStatus.SUCCESS
+ assert response.json() = {...} # some expected data
+```
+
+For all other tests, do not use `py_api` but call the implementing function directly. For example, do not call `client.get("/datasets/1")` but instead `get_dataset`:
+
+```python
+async def test_get_dataset_private_success(expdb_test: AsyncConnection, user_test: AsyncConnection) -> None: private_dataset = 42
+ owner_of_that_datset = OWNER_USER
+ dataset = await get_dataset(dataset_id=42, user=owner_of_that_dataset, user_db=user_test, expdb_db=expdb_test)
+ assert dataset.id == private_dataset
+
+async def test_get_dataset_private_access_denied(expdb_test: AsyncConnection, user_test: AsyncConnection) -> None:
+ private_dataset = 42
+ owner_of_that_datset = OWNER_USER
+ with pytest.raises(DatasetNoAccessError) as e:
+ await get_dataset(dataset_id=42, user=owner_of_that_dataset, user_db=user_test, expdb_db=expdb_test)
+ assert e.value.status_code == HTTPStatus.FORBIDDEN
+```
+
+*note:* We will likely mock the database layer at some point, but it's still taking shape.
+
+For the initial development of this API, we want to have a clear mapping from PHP API output to the new output.
+We also want to be aware of quirks that the PHP API might have.
+For both these reasons, we write what we call "migration" tests: they call both APIs with a variety of input and compare the result.
+Note that in some cases, there are some quite significant differences between the PHP and the Python based API.
+That's okay, but in that case we want to "document" the behavior of both in the test.
+Please reference a few implemented migration tests to get a better understanding, but here is a high level sketch:
+
```
+async def test_get_dataset(py_api: httpx.AsyncClient, php_api: httpx.AsyncClient) --> None:
+ py_response, php_response = asyncio.gather([
+ py_api.get("/datasets/1"),
+ php_api.get("/data/1"),
+ ])
+
+ if py_response.status_code == HTTPStatus.SUCCESS and php_api.status_code == HTTPStatus.SUCCESS:
+ _assert_success_response_equal(py_response.json(), php_response.json())
+ else:
+ _assert_error_response_equal(py_response, php_response)
+
+def _assert_success_response_equal(py_json, php_json) --> None:
+ # PHP API returns numbers as strings
+ py_json = nested_num_to_str(py_json)
+
+ # There might be more differences which need addressing
+ # ...
+ # and then finally we compare the results to ensure the remaining data is identical
+ assert py_json == php_json
+
+def _assert_error_response_equal(py_response, php_response) --> None:
+ # There might be some translation of error codes
+ if py_response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY:
+ assert php_response.status_code == HTTPStatus.PRECONDITION_FAILED
+ elif ...:
+ ...
+ else:
+ assert py_response.status_code == php_response.status_code
+
+ # Python follows RFC9457 while PHP has a custom system:
+ assert py_response.json()["code"] == php_response.json()["error"]["code"]
-When individually adding/removing components, we measure (for 5000 repeats, n=1):
-
-| expdb | user | api | exp call | user call | api get | time (s) |
-|-------|------|-----|----------|-----------|---------|----------:|
-| ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | 1.78 |
-| ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | 3.45 |
-| ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | 3.22 |
-| ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | 298.48 |
-| ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | 4.44 |
-| ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | 285.69 |
-| ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | 4.91 |
-| ❌ | ✅ | ❌ | ❌ | ✅ | ❌ | 5.81 |
-| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 307.91 |
-
-Adding a fixture that just returns some value adds only minimal overhead (1.91s),
-so the burden comes from establishing the database connection itself.
-
-We make the following observations:
-
-- Adding a database fixture adds the same overhead as instantiating an entirely new test.
-- Overhead of adding multiple database fixtures is not free
-- The `py_api` fixture adds two orders of magnitude more overhead
-
-We want our tests to be fast, so we want to avoid using these fixtures when we reasonably can.
-We restrict usage of `py_api` fixtures to integration/migration tests, since it is very slow.
-These only run on CI before merges.
-For database fixtures
-
-We will write some fixtures that can be used to e.g., get a `User` without accessing the database.
-The validity of these users will be tested against the database in only a single test.
-
-### Mocking
-Mocking can help us reduce the reliance on database connections in tests.
-A mocked function can prevent accessing the database, and instead return a predefined value instead.
-
-It has a few upsides:
- - It's faster than using a database fixture (see below).
- - The test is not dependent on the database: you can run the test without a database.
-
-But it also has downsides:
- - Behavior changes in the database, such as schema changes, are not automatically reflected in the tests.
- - The database layer (e.g., queries) are not actually tested.
-
-Basically, the mocked behavior may not match real behavior when executed on a database.
-For this reason, for each mocked entity, we should add a test that verifies that if the database layer
-is invoked with the database, it returns the expected output that matches the mock.
-This is additional overhead in development, but hopefully it pays back in more granular test feedback and faster tests.
-
-On the speed of mocks, consider these two tests:
-
-```diff
-@pytest.mark.parametrize('execution_number', range(5000))
-def test_private_dataset_owner_access(
- execution_number,
- admin,
-+ mocker,
-- expdb_test: Connection,
-) -> None:
-+ mock = mocker.patch('database.datasets.get')
-+ class Dataset(NamedTuple):
-+ uploader: int
-+ visibility: Visibility
-+ mock.return_value = Dataset(uploader=1, visibility=Visibility.PRIVATE)
-
- _get_dataset_raise_otherwise(
- dataset_id=1,
- user=admin,
-- expdb=expdb_test,
-+ expdb=None,
- )
```
-There is only a single database call in the test. It fetches a record on an indexed field and does not require any joins.
-Despite the database call being very light, the database-included test is ~50% slower than the mocked version (3.50s vs 5.04s).
diff --git a/pyproject.toml b/pyproject.toml
index 565f58d..d78d3d2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -121,11 +121,14 @@ asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "session"
asyncio_default_test_loop_scope = "session"
markers = [
- "slow: test or sets of tests which take more than a few seconds to run.",
+ "slow: test or sets of tests which take more than 0.1 second to run.",
# While the `mut`ation marker below is not strictly necessary as every change is
- # executed within transaction that is rolled back, it can halt other unit tests which
+ # executed within transaction that is rolled back, it can halt other unit tests
# whose queries may depend on the execution or rollback of the transaction.
- "mut: executes a mutation on the database (in a transaction which is rolled back)",
+ "mut: executes a mutation on the database (even when it is cleaned up).",
+ # *_api markers included for documentation, should not be added manually.
+ "py_api: requests the py_api fixture. Added automatically.",
+ "php_api: requests the py_api fixture. Added automatically.",
]
filterwarnings = [
'ignore:A private pytest class or function was used.:DeprecationWarning:tests.conftest:',
From 272a0745eb585633fbf67b14ff3ba063e5f44651 Mon Sep 17 00:00:00 2001
From: PGijsbers
Date: Tue, 14 Apr 2026 15:52:14 +0200
Subject: [PATCH 3/7] Fix typos, elaboreate on some points, give run example
---
docs/contributing/tests.md | 50 +++++++++++++++++++++++++++++---------
1 file changed, 38 insertions(+), 12 deletions(-)
diff --git a/docs/contributing/tests.md b/docs/contributing/tests.md
index a07f7e5..2f31192 100644
--- a/docs/contributing/tests.md
+++ b/docs/contributing/tests.md
@@ -8,6 +8,26 @@ It assumes you already followed the instructions on the ["Setup"](setup.md) page
Some tests and files in the current test suite need to be updated to reflect these conventions.
This documentation specifies the desired way of doing things even if the source code does something else.
+## Running Tests
+
+Following the setup page, you can run tests when the services are running.
+Start the services using one of two commands:
+
+```bash
+docker compose --profile apis up -d # if you run tests which need PHP REST API
+docker compose up python-api -d # if you do not run tests that require the PHP API
+```
+and then invoke pytest:
+```bash
+docker compose exec python-api python -m pytest tests
+```
+As of writing, the full test suite ran in sequence takes about 2 minutes.
+We generally recommend running subsets of the tests during development.
+In particular, running only the tests which do not use fuzzing or the PHP API takes under 3 seconds:
+```
+docker compose exec python-api python -m pytest tests -m "not php_api and not slow"
+```
+
## Writing Tests
We use the ubiquitous [Pytest](https://docs.pytest.org) framework when writing tests.
@@ -15,7 +35,7 @@ We use the ubiquitous [Pytest](https://docs.pytest.org) framework when writing t
### File Structure
When writing tests, we have the following additional conventions on the file structure:
- - Use a `_test` suffix when naming our files (not a `test_` prefix)
+ - Use a `_test` suffix when naming our files (not a `test_` prefix). Our tests already exist in a `tests` directory, and in common tree list side panels it's likely you can only see the start of file names, so this is more informative.
- One dedicated test file per endpoint
@@ -25,7 +45,12 @@ Some guidelines and things to keep in mind when writing tests:
- Try to keep tests small, so that they fail for one particular reason only.
- Mark tests that update the database in anyway with the `mut` marker (`@pytest.mark.mut`).
- If the test is excessively slow (>0.1 sec), use a `slow` marker.
- - The `py_api` and database fixtures have considerable per-test overhead. Use them only when you need them.
+ - Four common fixtures you might need when writing tests are:
+ - py_api: an async client for the Python based REST API
+ - php_api: an async client for the PHP based REST API
+ - expdb_test: an AsyncConnection to the "expdb" OpenML database.
+ - user_test: an AsyncConnection to the "openml" OpenML database.
+ - Above fixtures have considerable per-test overhead. Use them only when you need them.
- When writing assertions the expected value (a constant, or a php response) should be on the right (`assert response == expected`).
### Writing Tests for an Endpoint
@@ -45,20 +70,21 @@ Include one test against the `py_api` that confirms a successful request has the
def test_get_dataset_success(py_api: httpx.AsyncClient) -> None:
response = py_api.get("/datasets/1")
assert response.status_code == HTTPStatus.SUCCESS
- assert response.json() = {...} # some expected data
+ assert response.json() == {...} # some expected data
```
For all other tests, do not use `py_api` but call the implementing function directly. For example, do not call `client.get("/datasets/1")` but instead `get_dataset`:
```python
-async def test_get_dataset_private_success(expdb_test: AsyncConnection, user_test: AsyncConnection) -> None: private_dataset = 42
- owner_of_that_datset = OWNER_USER
+async def test_get_dataset_private_success(expdb_test: AsyncConnection, user_test: AsyncConnection) -> None:
+ private_dataset = 42
+ owner_of_that_dataset = OWNER_USER
dataset = await get_dataset(dataset_id=42, user=owner_of_that_dataset, user_db=user_test, expdb_db=expdb_test)
assert dataset.id == private_dataset
async def test_get_dataset_private_access_denied(expdb_test: AsyncConnection, user_test: AsyncConnection) -> None:
private_dataset = 42
- owner_of_that_datset = OWNER_USER
+ owner_of_that_dataset = OWNER_USER # Test User defined in a common file
with pytest.raises(DatasetNoAccessError) as e:
await get_dataset(dataset_id=42, user=owner_of_that_dataset, user_db=user_test, expdb_db=expdb_test)
assert e.value.status_code == HTTPStatus.FORBIDDEN
@@ -74,18 +100,18 @@ That's okay, but in that case we want to "document" the behavior of both in the
Please reference a few implemented migration tests to get a better understanding, but here is a high level sketch:
```
-async def test_get_dataset(py_api: httpx.AsyncClient, php_api: httpx.AsyncClient) --> None:
- py_response, php_response = asyncio.gather([
+async def test_get_dataset(py_api: httpx.AsyncClient, php_api: httpx.AsyncClient) -> None:
+ py_response, php_response = await asyncio.gather(
py_api.get("/datasets/1"),
php_api.get("/data/1"),
- ])
+ )
- if py_response.status_code == HTTPStatus.SUCCESS and php_api.status_code == HTTPStatus.SUCCESS:
+ if py_response.status_code == HTTPStatus.SUCCESS and php_response.status_code == HTTPStatus.SUCCESS:
_assert_success_response_equal(py_response.json(), php_response.json())
else:
_assert_error_response_equal(py_response, php_response)
-def _assert_success_response_equal(py_json, php_json) --> None:
+def _assert_success_response_equal(py_json, php_json) -> None:
# PHP API returns numbers as strings
py_json = nested_num_to_str(py_json)
@@ -94,7 +120,7 @@ def _assert_success_response_equal(py_json, php_json) --> None:
# and then finally we compare the results to ensure the remaining data is identical
assert py_json == php_json
-def _assert_error_response_equal(py_response, php_response) --> None:
+def _assert_error_response_equal(py_response, php_response) -> None:
# There might be some translation of error codes
if py_response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY:
assert php_response.status_code == HTTPStatus.PRECONDITION_FAILED
From f45a7c82ec0041ff36fd934ec8051fa3ff114b47 Mon Sep 17 00:00:00 2001
From: PGijsbers
Date: Tue, 14 Apr 2026 15:58:56 +0200
Subject: [PATCH 4/7] Clarify the 'slow' marker is only for tests that do not
use PHP
---
docs/contributing/documentation.md | 2 +-
docs/contributing/tests.md | 2 +-
mkdocs.yml | 2 +-
pyproject.toml | 3 ++-
4 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/docs/contributing/documentation.md b/docs/contributing/documentation.md
index 5f31980..cbeb1a4 100644
--- a/docs/contributing/documentation.md
+++ b/docs/contributing/documentation.md
@@ -19,7 +19,7 @@ For larger changes, clone a fork of the repository as described in the
After cloning the repository, you may also build and serve the documentation through Docker:
```
- docker compose up docs
+ docker compose -f compose.yaml -f compose.ports.yaml up docs
```
diff --git a/docs/contributing/tests.md b/docs/contributing/tests.md
index 2f31192..c2b9570 100644
--- a/docs/contributing/tests.md
+++ b/docs/contributing/tests.md
@@ -44,7 +44,7 @@ Some guidelines and things to keep in mind when writing tests:
- Try to keep tests small, so that they fail for one particular reason only.
- Mark tests that update the database in anyway with the `mut` marker (`@pytest.mark.mut`).
- - If the test is excessively slow (>0.1 sec), use a `slow` marker.
+ - If the test is excessively slow (>0.1 sec) and does not connect to PHP, use a `slow` marker. Tests always require roundtrips through other services which makes them slow by default. These tests can be filtered out with the automatically generated "php_api" marker.
- Four common fixtures you might need when writing tests are:
- py_api: an async client for the Python based REST API
- php_api: an async client for the PHP based REST API
diff --git a/mkdocs.yml b/mkdocs.yml
index 23be6b1..7e9df19 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -21,7 +21,7 @@ nav:
- Changes: migration.md
- Contributing:
- contributing/index.md
- - Development: contributing/contributing.md
+ - Setup: contributing/setup.md
- Tests: contributing/tests.md
- Documentation: contributing/documentation.md
- Project Overview: contributing/project_overview.md
diff --git a/pyproject.toml b/pyproject.toml
index d78d3d2..111a064 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -121,7 +121,8 @@ asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "session"
asyncio_default_test_loop_scope = "session"
markers = [
- "slow: test or sets of tests which take more than 0.1 second to run.",
+ # We exclude PHP tests because they are quite slow to begin with.
+ "slow: python-only test or sets of tests which take more than 0.1 second to run.",
# While the `mut`ation marker below is not strictly necessary as every change is
# executed within transaction that is rolled back, it can halt other unit tests
# whose queries may depend on the execution or rollback of the transaction.
From c7fa0e1b84f6f62bb8850ffc2577f070f9d0154a Mon Sep 17 00:00:00 2001
From: PGijsbers
Date: Tue, 14 Apr 2026 16:13:33 +0200
Subject: [PATCH 5/7] Fix typos
---
docs/contributing/tests.md | 8 ++++----
pyproject.toml | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/contributing/tests.md b/docs/contributing/tests.md
index c2b9570..cfd48bc 100644
--- a/docs/contributing/tests.md
+++ b/docs/contributing/tests.md
@@ -60,7 +60,7 @@ follow these guidelines for writing a test suite for an endpoint.
Include tests against `py_api` for input validation specific to that endpoint. Validation in reused components should be tested centrally (e.g., Pagination).
```python
def test_get_dataset_identifier_validation(py_api: httpx.AsyncClient) -> None:
- response = py_api.get("/datasets/not-an-integer")
+ response = await py_api.get("/datasets/not-an-integer")
assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
```
@@ -68,8 +68,8 @@ Include one test against the `py_api` that confirms a successful request has the
```python
def test_get_dataset_success(py_api: httpx.AsyncClient) -> None:
- response = py_api.get("/datasets/1")
- assert response.status_code == HTTPStatus.SUCCESS
+ response = await py_api.get("/datasets/1")
+ assert response.status_code == HTTPStatus.OK
assert response.json() == {...} # some expected data
```
@@ -106,7 +106,7 @@ async def test_get_dataset(py_api: httpx.AsyncClient, php_api: httpx.AsyncClient
php_api.get("/data/1"),
)
- if py_response.status_code == HTTPStatus.SUCCESS and php_response.status_code == HTTPStatus.SUCCESS:
+ if py_response.status_code == HTTPStatus.OK and php_response.status_code == HTTPStatus.OK:
_assert_success_response_equal(py_response.json(), php_response.json())
else:
_assert_error_response_equal(py_response, php_response)
diff --git a/pyproject.toml b/pyproject.toml
index 111a064..d8078bf 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -129,7 +129,7 @@ markers = [
"mut: executes a mutation on the database (even when it is cleaned up).",
# *_api markers included for documentation, should not be added manually.
"py_api: requests the py_api fixture. Added automatically.",
- "php_api: requests the py_api fixture. Added automatically.",
+ "php_api: requests the php_api fixture. Added automatically.",
]
filterwarnings = [
'ignore:A private pytest class or function was used.:DeprecationWarning:tests.conftest:',
From 36c4920d5cf33e1166fe80b96af72c771605ab2a Mon Sep 17 00:00:00 2001
From: PGijsbers
Date: Tue, 14 Apr 2026 16:17:08 +0200
Subject: [PATCH 6/7] Add language tags
---
docs/contributing/tests.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/contributing/tests.md b/docs/contributing/tests.md
index cfd48bc..1f3ad7a 100644
--- a/docs/contributing/tests.md
+++ b/docs/contributing/tests.md
@@ -24,7 +24,7 @@ docker compose exec python-api python -m pytest tests
As of writing, the full test suite ran in sequence takes about 2 minutes.
We generally recommend running subsets of the tests during development.
In particular, running only the tests which do not use fuzzing or the PHP API takes under 3 seconds:
-```
+```bash
docker compose exec python-api python -m pytest tests -m "not php_api and not slow"
```
@@ -99,7 +99,7 @@ Note that in some cases, there are some quite significant differences between th
That's okay, but in that case we want to "document" the behavior of both in the test.
Please reference a few implemented migration tests to get a better understanding, but here is a high level sketch:
-```
+```python
async def test_get_dataset(py_api: httpx.AsyncClient, php_api: httpx.AsyncClient) -> None:
py_response, php_response = await asyncio.gather(
py_api.get("/datasets/1"),
From 502ec0324f3fe6043c1c22d09a43fc4d95e6cb08 Mon Sep 17 00:00:00 2001
From: PGijsbers
Date: Tue, 14 Apr 2026 16:19:33 +0200
Subject: [PATCH 7/7] Minor edits for clarity
---
docs/contributing/tests.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/contributing/tests.md b/docs/contributing/tests.md
index 1f3ad7a..066ea27 100644
--- a/docs/contributing/tests.md
+++ b/docs/contributing/tests.md
@@ -44,7 +44,7 @@ Some guidelines and things to keep in mind when writing tests:
- Try to keep tests small, so that they fail for one particular reason only.
- Mark tests that update the database in anyway with the `mut` marker (`@pytest.mark.mut`).
- - If the test is excessively slow (>0.1 sec) and does not connect to PHP, use a `slow` marker. Tests always require roundtrips through other services which makes them slow by default. These tests can be filtered out with the automatically generated "php_api" marker.
+ - If the test is excessively slow (>0.1 sec) and does not connect to PHP, use a `slow` marker. Tests that include PHP always require roundtrips through other services which makes them slow by default. PHP tests can be filtered out with the automatically generated "php_api" marker.
- Four common fixtures you might need when writing tests are:
- py_api: an async client for the Python based REST API
- php_api: an async client for the PHP based REST API
@@ -84,7 +84,7 @@ async def test_get_dataset_private_success(expdb_test: AsyncConnection, user_tes
async def test_get_dataset_private_access_denied(expdb_test: AsyncConnection, user_test: AsyncConnection) -> None:
private_dataset = 42
- owner_of_that_dataset = OWNER_USER # Test User defined in a common file
+ owner_of_that_dataset = SOME_USER # Test User defined in a common file
with pytest.raises(DatasetNoAccessError) as e:
await get_dataset(dataset_id=42, user=owner_of_that_dataset, user_db=user_test, expdb_db=expdb_test)
assert e.value.status_code == HTTPStatus.FORBIDDEN