spltz-viur-testing 0.1.0__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 (27) hide show
  1. spltz_viur_testing-0.1.0/LICENSE +21 -0
  2. spltz_viur_testing-0.1.0/PKG-INFO +308 -0
  3. spltz_viur_testing-0.1.0/README.md +251 -0
  4. spltz_viur_testing-0.1.0/pyproject.toml +88 -0
  5. spltz_viur_testing-0.1.0/setup.cfg +4 -0
  6. spltz_viur_testing-0.1.0/src/spltz_viur_testing.egg-info/PKG-INFO +308 -0
  7. spltz_viur_testing-0.1.0/src/spltz_viur_testing.egg-info/SOURCES.txt +25 -0
  8. spltz_viur_testing-0.1.0/src/spltz_viur_testing.egg-info/dependency_links.txt +1 -0
  9. spltz_viur_testing-0.1.0/src/spltz_viur_testing.egg-info/requires.txt +15 -0
  10. spltz_viur_testing-0.1.0/src/spltz_viur_testing.egg-info/top_level.txt +1 -0
  11. spltz_viur_testing-0.1.0/src/viur/testing/__init__.py +321 -0
  12. spltz_viur_testing-0.1.0/src/viur/testing/_test/__init__.py +144 -0
  13. spltz_viur_testing-0.1.0/src/viur/testing/_test/config.py +350 -0
  14. spltz_viur_testing-0.1.0/src/viur/testing/activation.py +330 -0
  15. spltz_viur_testing-0.1.0/src/viur/testing/banner.py +145 -0
  16. spltz_viur_testing-0.1.0/src/viur/testing/constants.py +40 -0
  17. spltz_viur_testing-0.1.0/src/viur/testing/protection.py +39 -0
  18. spltz_viur_testing-0.1.0/src/viur/testing/runner.py +202 -0
  19. spltz_viur_testing-0.1.0/src/viur/testing/validator.py +121 -0
  20. spltz_viur_testing-0.1.0/tests/test_activation.py +664 -0
  21. spltz_viur_testing-0.1.0/tests/test_banner.py +308 -0
  22. spltz_viur_testing-0.1.0/tests/test_config_module.py +474 -0
  23. spltz_viur_testing-0.1.0/tests/test_package.py +341 -0
  24. spltz_viur_testing-0.1.0/tests/test_protection.py +30 -0
  25. spltz_viur_testing-0.1.0/tests/test_runner.py +255 -0
  26. spltz_viur_testing-0.1.0/tests/test_test_container.py +191 -0
  27. spltz_viur_testing-0.1.0/tests/test_validator.py +202 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright © 2026 Andreas H. Kelch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,308 @@
1
+ Metadata-Version: 2.4
2
+ Name: spltz-viur-testing
3
+ Version: 0.1.0
4
+ Summary: Safe test-mode for ViUR core projects — Playwright e2e ready.
5
+ Author: Andreas H. Kelch
6
+ License: MIT License
7
+
8
+ Copyright © 2026 Andreas H. Kelch
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/sprengplatz/viur-testing
29
+ Project-URL: Documentation, https://sprengplatz.github.io/viur-testing/
30
+ Project-URL: Repository, https://github.com/sprengplatz/viur-testing.git
31
+ Project-URL: Bug Tracker, https://github.com/sprengplatz/viur-testing/issues
32
+ Project-URL: Changelog, https://github.com/sprengplatz/viur-testing/blob/main/CHANGELOG.md
33
+ Keywords: viur,e2e,testing,playwright
34
+ Classifier: Development Status :: 4 - Beta
35
+ Classifier: Intended Audience :: Developers
36
+ Classifier: License :: OSI Approved :: MIT License
37
+ Classifier: Operating System :: OS Independent
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Programming Language :: Python :: 3.13
40
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
41
+ Requires-Python: >=3.12
42
+ Description-Content-Type: text/markdown
43
+ License-File: LICENSE
44
+ Requires-Dist: viur-core<4,>=3.7
45
+ Provides-Extra: test
46
+ Requires-Dist: pytest~=8.0; extra == "test"
47
+ Requires-Dist: pytest-cov~=5.0; extra == "test"
48
+ Requires-Dist: coverage[toml]~=7.0; extra == "test"
49
+ Requires-Dist: spltz-viur-light-mock>=0.1; extra == "test"
50
+ Provides-Extra: docs
51
+ Requires-Dist: mkdocs-material~=9.5; extra == "docs"
52
+ Requires-Dist: mkdocstrings[python]~=0.26; extra == "docs"
53
+ Provides-Extra: dev
54
+ Requires-Dist: spltz-viur-testing[docs,test]; extra == "dev"
55
+ Requires-Dist: build~=1.2; extra == "dev"
56
+ Dynamic: license-file
57
+
58
+ # viur-testing
59
+
60
+ [![Tests](https://github.com/sprengplatz/viur-testing/actions/workflows/test.yml/badge.svg)](https://github.com/sprengplatz/viur-testing/actions/workflows/test.yml)
61
+ [![Docs](https://github.com/sprengplatz/viur-testing/actions/workflows/docs.yml/badge.svg)](https://sprengplatz.github.io/viur-testing/)
62
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
63
+
64
+ Safe test-mode for ViUR core projects — primarily for Playwright
65
+ end-to-end tests. Swaps the default datastore database out for a
66
+ dedicated named database (default: `viur-tests`), with a bilateral
67
+ handshake that refuses to let any test or endpoint run unless both the
68
+ server **and** the runner agree they are talking to the test instance.
69
+
70
+ ## Bilateral guarantee in six lines
71
+
72
+ 1. **`activate()` refuses outside `conf.instance.is_dev_server`** —
73
+ read from viur-core's canonical flag.
74
+ 2. **Synchronous datastore probe** in the target database has to
75
+ succeed before the client swap is applied.
76
+ 3. **`TestModule` *and* `ConfigModule` both refuse to instantiate**
77
+ outside the dev server *or* without a prior `activate()` call —
78
+ both `__init__`s raise in either case. Even a host that bypasses
79
+ the `TestModule` container and mounts `ConfigModule` directly is
80
+ subject to the same checks, so a forgotten activate or a stray
81
+ production mount fails loudly at boot.
82
+ 4. **Per-request token validator** (`X-Viur-Test-Token`) blocks every
83
+ request that does not carry the session token, except the two
84
+ bootstrap endpoints.
85
+ 5. **`protect()` installs a production-side header guard** that 403s
86
+ any request carrying the test token header outside dev, regardless
87
+ of its value. Installed in every environment.
88
+ 6. **Runner preflight** calls `/json/_test/config/status` and refuses to run
89
+ any test if the server's reply (database, project_id, token hash)
90
+ does not match.
91
+
92
+ The session token is stored only in the test database itself
93
+ (kind `viur-tests`, entity `auth-token`) — never on disk. App Engine
94
+ file-system writes are not needed.
95
+
96
+ ## Module layout
97
+
98
+ ```
99
+ /_test/ TestModule (container, refuses outside dev mode)
100
+ /_test/config/status ConfigModule.status — issues/returns token
101
+ /_test/config/finish ConfigModule.finish — deletes token entity
102
+ ```
103
+
104
+ Future test flavours (load test, integration helpers, …) go in as
105
+ sibling submodules under the same `_test` container.
106
+
107
+ ## Requirements
108
+
109
+ - Python ≥ 3.12
110
+ - viur-core ≥ 3.7, < 4
111
+ - A named Datastore database (default name: `viur-tests`) created in
112
+ your GCP project alongside `(default)`.
113
+
114
+ ## Install
115
+
116
+ The PyPI distribution name is `spltz-viur-testing` (experimental
117
+ prefix); the Python import path stays `viur.testing`:
118
+
119
+ ```sh
120
+ pip install spltz-viur-testing
121
+ ```
122
+
123
+ ```python
124
+ import viur.testing
125
+ viur.testing.setup()
126
+ ```
127
+
128
+ ## Server-side wiring
129
+
130
+ Two one-liners. `main.py` — **as the first lines, before any
131
+ `viur.core` import**:
132
+
133
+ ```python
134
+ import viur.testing
135
+ viur.testing.setup()
136
+
137
+ # Only now may viur.core be imported by your own code.
138
+ from viur.core import setup as core_setup
139
+ import modules
140
+ import render
141
+
142
+ core_setup(modules, render)
143
+ ```
144
+
145
+ `viur.testing.setup()` checks the `VIUR_TESTING_ENABLE` env var; if
146
+ truthy it calls `activate()` (datastore client swap + key-factory
147
+ patch + closed-system whitelist + state priming + validator install)
148
+ and always installs the production header guard via `protect()`.
149
+
150
+ In `modules/__init__.py` register the test endpoints — idempotent and
151
+ safe to leave in place for production deployments (no-op when test
152
+ mode isn't armed):
153
+
154
+ ```python
155
+ # modules/__init__.py
156
+ import viur.testing
157
+ viur.testing.register_modules(globals())
158
+ ```
159
+
160
+ That exposes `POST /json/_test/config/status` and `POST /json/_test/config/finish`.
161
+ Both endpoints re-verify `conf.instance.is_dev_server` and the active
162
+ datastore database before performing any work — defence in depth on
163
+ top of the `TestModule.__init__` guard.
164
+
165
+ If you need more control, the two functions wrap underlying primitives
166
+ you can call yourself: `viur.testing.activate(database=...)`,
167
+ `viur.testing.protect()`, plus direct mounting via
168
+ `from viur.testing._test import TestModule`.
169
+
170
+ ## Running the dev server with test mode
171
+
172
+ Toggle test mode at boot by setting the env var that `setup()` reads:
173
+
174
+ ```sh
175
+ VIUR_TESTING_ENABLE=1 viur run
176
+ ```
177
+
178
+ Without the env var, `setup()` skips `activate()` and the process boots
179
+ against the default database as if the package were not installed.
180
+
181
+ When test mode is active, the dev-server boot banner gains two extra
182
+ lines — `database = …` and `namespace = …` — so the running slice is
183
+ visible at a glance. The namespace line is rendered unconditionally;
184
+ without `VIUR_TESTING_NAMESPACE` it falls back to `(default)`, making
185
+ it obvious that test mode is armed but namespace isolation is **not**
186
+ in effect:
187
+
188
+ ```
189
+ # With VIUR_TESTING_NAMESPACE=alice
190
+ ################## LOCAL DEVELOPMENT SERVER IS UP AND RUNNING ##################
191
+ # project = my-viur-project #
192
+ # python = 3.13.0 #
193
+ # viur = 3.8.25 #
194
+ # database = viur-tests #
195
+ # namespace = alice #
196
+ ################################################################################
197
+
198
+ # Without VIUR_TESTING_NAMESPACE (or with empty value)
199
+ ################## LOCAL DEVELOPMENT SERVER IS UP AND RUNNING ##################
200
+ # project = my-viur-project #
201
+ # python = 3.13.0 #
202
+ # viur = 3.8.25 #
203
+ # database = viur-tests #
204
+ # namespace = (default) #
205
+ ################################################################################
206
+ ```
207
+
208
+ ## Concurrency: sharing one test database across multiple testers
209
+
210
+ The `viur-tests` database is a shared GCP resource. If two engineers
211
+ both boot a dev server with the same database and run tests at the
212
+ same time, their entities will collide — Person A's seed wipes
213
+ Person B's user, Person B's test queries find leftovers from Person A.
214
+
215
+ The fix is the optional `namespace` argument. ViUR-testing passes it
216
+ to `google.cloud.datastore.Client(database=…, namespace=…)` and rewires
217
+ `viur.core.db.Key` so every read and write in the process is scoped
218
+ to that namespace. Different namespaces in the same database are
219
+ fully isolated — no separate DB provisioning needed.
220
+
221
+ Boot each dev server with its own namespace:
222
+
223
+ ```sh
224
+ # Alice's machine
225
+ VIUR_TESTING_ENABLE=1 VIUR_TESTING_NAMESPACE=alice viur run
226
+
227
+ # Bob's machine
228
+ VIUR_TESTING_ENABLE=1 VIUR_TESTING_NAMESPACE=bob viur run
229
+
230
+ # CI for PR #42
231
+ VIUR_TESTING_ENABLE=1 VIUR_TESTING_NAMESPACE=ci-pr-42 viur run
232
+ ```
233
+
234
+ The runner-side `require_test_mode` can assert the expected namespace
235
+ to fail fast when somebody points at the wrong slice::
236
+
237
+ from viur.testing import require_test_mode
238
+
239
+ status = require_test_mode(
240
+ "http://localhost:8080",
241
+ expected_namespace="alice", # omit to skip; pass None for default
242
+ )
243
+
244
+ `VIUR_TESTING_NAMESPACE` may be empty or unset — both mean "no
245
+ namespace, use the Datastore default". This is the existing behaviour
246
+ when no namespaces are needed (e.g. single-developer setup).
247
+
248
+ ## Runner-side wiring (pytest + Playwright)
249
+
250
+ ```python
251
+ # tests/conftest.py
252
+ import pytest
253
+ from viur.testing import require_test_mode, finish
254
+
255
+ _BASE_URL = "http://localhost:8080"
256
+
257
+
258
+ @pytest.fixture(scope="session")
259
+ def test_session():
260
+ status = require_test_mode(_BASE_URL)
261
+ yield status
262
+ finish(_BASE_URL, status.token)
263
+ ```
264
+
265
+ If the server is not in test mode, points at the wrong database, or
266
+ the token hash does not match the returned token, `require_test_mode`
267
+ raises `TestModePreflightError` and no test ever runs.
268
+
269
+ For Playwright, inject the token as a default header on the browser
270
+ context:
271
+
272
+ ```python
273
+ @pytest.fixture
274
+ def context(browser, test_session):
275
+ return browser.new_context(
276
+ extra_http_headers={"X-Viur-Test-Token": test_session.token},
277
+ )
278
+ ```
279
+
280
+ ## Naming
281
+
282
+ The Python package keeps its repository name `viur-testing` for stability,
283
+ but everything inside it speaks the generic *test* vocabulary so future
284
+ test flavours can be added under the same `TestModule` umbrella
285
+ without churn. The `_test` URL prefix's leading underscore signals
286
+ "system-internal, not for production callers".
287
+
288
+ ## Development
289
+
290
+ ```bash
291
+ git clone git@github.com:sprengplatz/viur-testing.git
292
+ cd viur-testing
293
+ pip install -e ".[dev]"
294
+ pytest # 100% coverage required
295
+ mkdocs serve # docs at http://localhost:8000
296
+ ```
297
+
298
+ ## Documentation
299
+
300
+ See [sprengplatz.github.io/viur-testing](https://sprengplatz.github.io/viur-testing/).
301
+
302
+ ## Changelog
303
+
304
+ See [CHANGELOG.md](CHANGELOG.md).
305
+
306
+ ## License
307
+
308
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,251 @@
1
+ # viur-testing
2
+
3
+ [![Tests](https://github.com/sprengplatz/viur-testing/actions/workflows/test.yml/badge.svg)](https://github.com/sprengplatz/viur-testing/actions/workflows/test.yml)
4
+ [![Docs](https://github.com/sprengplatz/viur-testing/actions/workflows/docs.yml/badge.svg)](https://sprengplatz.github.io/viur-testing/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+
7
+ Safe test-mode for ViUR core projects — primarily for Playwright
8
+ end-to-end tests. Swaps the default datastore database out for a
9
+ dedicated named database (default: `viur-tests`), with a bilateral
10
+ handshake that refuses to let any test or endpoint run unless both the
11
+ server **and** the runner agree they are talking to the test instance.
12
+
13
+ ## Bilateral guarantee in six lines
14
+
15
+ 1. **`activate()` refuses outside `conf.instance.is_dev_server`** —
16
+ read from viur-core's canonical flag.
17
+ 2. **Synchronous datastore probe** in the target database has to
18
+ succeed before the client swap is applied.
19
+ 3. **`TestModule` *and* `ConfigModule` both refuse to instantiate**
20
+ outside the dev server *or* without a prior `activate()` call —
21
+ both `__init__`s raise in either case. Even a host that bypasses
22
+ the `TestModule` container and mounts `ConfigModule` directly is
23
+ subject to the same checks, so a forgotten activate or a stray
24
+ production mount fails loudly at boot.
25
+ 4. **Per-request token validator** (`X-Viur-Test-Token`) blocks every
26
+ request that does not carry the session token, except the two
27
+ bootstrap endpoints.
28
+ 5. **`protect()` installs a production-side header guard** that 403s
29
+ any request carrying the test token header outside dev, regardless
30
+ of its value. Installed in every environment.
31
+ 6. **Runner preflight** calls `/json/_test/config/status` and refuses to run
32
+ any test if the server's reply (database, project_id, token hash)
33
+ does not match.
34
+
35
+ The session token is stored only in the test database itself
36
+ (kind `viur-tests`, entity `auth-token`) — never on disk. App Engine
37
+ file-system writes are not needed.
38
+
39
+ ## Module layout
40
+
41
+ ```
42
+ /_test/ TestModule (container, refuses outside dev mode)
43
+ /_test/config/status ConfigModule.status — issues/returns token
44
+ /_test/config/finish ConfigModule.finish — deletes token entity
45
+ ```
46
+
47
+ Future test flavours (load test, integration helpers, …) go in as
48
+ sibling submodules under the same `_test` container.
49
+
50
+ ## Requirements
51
+
52
+ - Python ≥ 3.12
53
+ - viur-core ≥ 3.7, < 4
54
+ - A named Datastore database (default name: `viur-tests`) created in
55
+ your GCP project alongside `(default)`.
56
+
57
+ ## Install
58
+
59
+ The PyPI distribution name is `spltz-viur-testing` (experimental
60
+ prefix); the Python import path stays `viur.testing`:
61
+
62
+ ```sh
63
+ pip install spltz-viur-testing
64
+ ```
65
+
66
+ ```python
67
+ import viur.testing
68
+ viur.testing.setup()
69
+ ```
70
+
71
+ ## Server-side wiring
72
+
73
+ Two one-liners. `main.py` — **as the first lines, before any
74
+ `viur.core` import**:
75
+
76
+ ```python
77
+ import viur.testing
78
+ viur.testing.setup()
79
+
80
+ # Only now may viur.core be imported by your own code.
81
+ from viur.core import setup as core_setup
82
+ import modules
83
+ import render
84
+
85
+ core_setup(modules, render)
86
+ ```
87
+
88
+ `viur.testing.setup()` checks the `VIUR_TESTING_ENABLE` env var; if
89
+ truthy it calls `activate()` (datastore client swap + key-factory
90
+ patch + closed-system whitelist + state priming + validator install)
91
+ and always installs the production header guard via `protect()`.
92
+
93
+ In `modules/__init__.py` register the test endpoints — idempotent and
94
+ safe to leave in place for production deployments (no-op when test
95
+ mode isn't armed):
96
+
97
+ ```python
98
+ # modules/__init__.py
99
+ import viur.testing
100
+ viur.testing.register_modules(globals())
101
+ ```
102
+
103
+ That exposes `POST /json/_test/config/status` and `POST /json/_test/config/finish`.
104
+ Both endpoints re-verify `conf.instance.is_dev_server` and the active
105
+ datastore database before performing any work — defence in depth on
106
+ top of the `TestModule.__init__` guard.
107
+
108
+ If you need more control, the two functions wrap underlying primitives
109
+ you can call yourself: `viur.testing.activate(database=...)`,
110
+ `viur.testing.protect()`, plus direct mounting via
111
+ `from viur.testing._test import TestModule`.
112
+
113
+ ## Running the dev server with test mode
114
+
115
+ Toggle test mode at boot by setting the env var that `setup()` reads:
116
+
117
+ ```sh
118
+ VIUR_TESTING_ENABLE=1 viur run
119
+ ```
120
+
121
+ Without the env var, `setup()` skips `activate()` and the process boots
122
+ against the default database as if the package were not installed.
123
+
124
+ When test mode is active, the dev-server boot banner gains two extra
125
+ lines — `database = …` and `namespace = …` — so the running slice is
126
+ visible at a glance. The namespace line is rendered unconditionally;
127
+ without `VIUR_TESTING_NAMESPACE` it falls back to `(default)`, making
128
+ it obvious that test mode is armed but namespace isolation is **not**
129
+ in effect:
130
+
131
+ ```
132
+ # With VIUR_TESTING_NAMESPACE=alice
133
+ ################## LOCAL DEVELOPMENT SERVER IS UP AND RUNNING ##################
134
+ # project = my-viur-project #
135
+ # python = 3.13.0 #
136
+ # viur = 3.8.25 #
137
+ # database = viur-tests #
138
+ # namespace = alice #
139
+ ################################################################################
140
+
141
+ # Without VIUR_TESTING_NAMESPACE (or with empty value)
142
+ ################## LOCAL DEVELOPMENT SERVER IS UP AND RUNNING ##################
143
+ # project = my-viur-project #
144
+ # python = 3.13.0 #
145
+ # viur = 3.8.25 #
146
+ # database = viur-tests #
147
+ # namespace = (default) #
148
+ ################################################################################
149
+ ```
150
+
151
+ ## Concurrency: sharing one test database across multiple testers
152
+
153
+ The `viur-tests` database is a shared GCP resource. If two engineers
154
+ both boot a dev server with the same database and run tests at the
155
+ same time, their entities will collide — Person A's seed wipes
156
+ Person B's user, Person B's test queries find leftovers from Person A.
157
+
158
+ The fix is the optional `namespace` argument. ViUR-testing passes it
159
+ to `google.cloud.datastore.Client(database=…, namespace=…)` and rewires
160
+ `viur.core.db.Key` so every read and write in the process is scoped
161
+ to that namespace. Different namespaces in the same database are
162
+ fully isolated — no separate DB provisioning needed.
163
+
164
+ Boot each dev server with its own namespace:
165
+
166
+ ```sh
167
+ # Alice's machine
168
+ VIUR_TESTING_ENABLE=1 VIUR_TESTING_NAMESPACE=alice viur run
169
+
170
+ # Bob's machine
171
+ VIUR_TESTING_ENABLE=1 VIUR_TESTING_NAMESPACE=bob viur run
172
+
173
+ # CI for PR #42
174
+ VIUR_TESTING_ENABLE=1 VIUR_TESTING_NAMESPACE=ci-pr-42 viur run
175
+ ```
176
+
177
+ The runner-side `require_test_mode` can assert the expected namespace
178
+ to fail fast when somebody points at the wrong slice::
179
+
180
+ from viur.testing import require_test_mode
181
+
182
+ status = require_test_mode(
183
+ "http://localhost:8080",
184
+ expected_namespace="alice", # omit to skip; pass None for default
185
+ )
186
+
187
+ `VIUR_TESTING_NAMESPACE` may be empty or unset — both mean "no
188
+ namespace, use the Datastore default". This is the existing behaviour
189
+ when no namespaces are needed (e.g. single-developer setup).
190
+
191
+ ## Runner-side wiring (pytest + Playwright)
192
+
193
+ ```python
194
+ # tests/conftest.py
195
+ import pytest
196
+ from viur.testing import require_test_mode, finish
197
+
198
+ _BASE_URL = "http://localhost:8080"
199
+
200
+
201
+ @pytest.fixture(scope="session")
202
+ def test_session():
203
+ status = require_test_mode(_BASE_URL)
204
+ yield status
205
+ finish(_BASE_URL, status.token)
206
+ ```
207
+
208
+ If the server is not in test mode, points at the wrong database, or
209
+ the token hash does not match the returned token, `require_test_mode`
210
+ raises `TestModePreflightError` and no test ever runs.
211
+
212
+ For Playwright, inject the token as a default header on the browser
213
+ context:
214
+
215
+ ```python
216
+ @pytest.fixture
217
+ def context(browser, test_session):
218
+ return browser.new_context(
219
+ extra_http_headers={"X-Viur-Test-Token": test_session.token},
220
+ )
221
+ ```
222
+
223
+ ## Naming
224
+
225
+ The Python package keeps its repository name `viur-testing` for stability,
226
+ but everything inside it speaks the generic *test* vocabulary so future
227
+ test flavours can be added under the same `TestModule` umbrella
228
+ without churn. The `_test` URL prefix's leading underscore signals
229
+ "system-internal, not for production callers".
230
+
231
+ ## Development
232
+
233
+ ```bash
234
+ git clone git@github.com:sprengplatz/viur-testing.git
235
+ cd viur-testing
236
+ pip install -e ".[dev]"
237
+ pytest # 100% coverage required
238
+ mkdocs serve # docs at http://localhost:8000
239
+ ```
240
+
241
+ ## Documentation
242
+
243
+ See [sprengplatz.github.io/viur-testing](https://sprengplatz.github.io/viur-testing/).
244
+
245
+ ## Changelog
246
+
247
+ See [CHANGELOG.md](CHANGELOG.md).
248
+
249
+ ## License
250
+
251
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,88 @@
1
+ [build-system]
2
+ requires = ["setuptools >= 61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "spltz-viur-testing"
7
+ dynamic = ["version"]
8
+ description = "Safe test-mode for ViUR core projects — Playwright e2e ready."
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ requires-python = ">=3.12"
12
+ authors = [
13
+ { name = "Andreas H. Kelch" },
14
+ ]
15
+ keywords = ["viur", "e2e", "testing", "playwright"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ ]
25
+ dependencies = [
26
+ "viur-core>=3.7,<4",
27
+ ]
28
+
29
+ [project.optional-dependencies]
30
+ test = [
31
+ "pytest~=8.0",
32
+ "pytest-cov~=5.0",
33
+ "coverage[toml]~=7.0",
34
+ "spltz-viur-light-mock>=0.1",
35
+ ]
36
+ docs = [
37
+ "mkdocs-material~=9.5",
38
+ "mkdocstrings[python]~=0.26",
39
+ ]
40
+ dev = [
41
+ "spltz-viur-testing[test,docs]",
42
+ "build~=1.2",
43
+ ]
44
+
45
+ [project.urls]
46
+ Homepage = "https://github.com/sprengplatz/viur-testing"
47
+ Documentation = "https://sprengplatz.github.io/viur-testing/"
48
+ Repository = "https://github.com/sprengplatz/viur-testing.git"
49
+ "Bug Tracker" = "https://github.com/sprengplatz/viur-testing/issues"
50
+ Changelog = "https://github.com/sprengplatz/viur-testing/blob/main/CHANGELOG.md"
51
+
52
+ [tool.setuptools.dynamic]
53
+ version = { attr = "viur.testing.__version__" }
54
+
55
+ [tool.setuptools.packages.find]
56
+ where = ["src"]
57
+ include = ["viur.*"]
58
+
59
+ [tool.pytest.ini_options]
60
+ minversion = "8.0"
61
+ testpaths = ["tests"]
62
+ addopts = [
63
+ "-ra",
64
+ "--strict-markers",
65
+ "--strict-config",
66
+ "--cov=viur.testing",
67
+ "--cov-report=term-missing",
68
+ "--cov-report=xml",
69
+ "--cov-report=html",
70
+ "--cov-fail-under=100",
71
+ ]
72
+ filterwarnings = [
73
+ "error",
74
+ ]
75
+
76
+ [tool.coverage.run]
77
+ branch = true
78
+ source = ["viur.testing"]
79
+
80
+ [tool.coverage.report]
81
+ exclude_also = [
82
+ "raise NotImplementedError",
83
+ "if TYPE_CHECKING:",
84
+ "if t\\.TYPE_CHECKING:",
85
+ "\\.\\.\\.",
86
+ ]
87
+ show_missing = true
88
+ skip_covered = false
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+