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.
- spltz_viur_testing-0.1.0/LICENSE +21 -0
- spltz_viur_testing-0.1.0/PKG-INFO +308 -0
- spltz_viur_testing-0.1.0/README.md +251 -0
- spltz_viur_testing-0.1.0/pyproject.toml +88 -0
- spltz_viur_testing-0.1.0/setup.cfg +4 -0
- spltz_viur_testing-0.1.0/src/spltz_viur_testing.egg-info/PKG-INFO +308 -0
- spltz_viur_testing-0.1.0/src/spltz_viur_testing.egg-info/SOURCES.txt +25 -0
- spltz_viur_testing-0.1.0/src/spltz_viur_testing.egg-info/dependency_links.txt +1 -0
- spltz_viur_testing-0.1.0/src/spltz_viur_testing.egg-info/requires.txt +15 -0
- spltz_viur_testing-0.1.0/src/spltz_viur_testing.egg-info/top_level.txt +1 -0
- spltz_viur_testing-0.1.0/src/viur/testing/__init__.py +321 -0
- spltz_viur_testing-0.1.0/src/viur/testing/_test/__init__.py +144 -0
- spltz_viur_testing-0.1.0/src/viur/testing/_test/config.py +350 -0
- spltz_viur_testing-0.1.0/src/viur/testing/activation.py +330 -0
- spltz_viur_testing-0.1.0/src/viur/testing/banner.py +145 -0
- spltz_viur_testing-0.1.0/src/viur/testing/constants.py +40 -0
- spltz_viur_testing-0.1.0/src/viur/testing/protection.py +39 -0
- spltz_viur_testing-0.1.0/src/viur/testing/runner.py +202 -0
- spltz_viur_testing-0.1.0/src/viur/testing/validator.py +121 -0
- spltz_viur_testing-0.1.0/tests/test_activation.py +664 -0
- spltz_viur_testing-0.1.0/tests/test_banner.py +308 -0
- spltz_viur_testing-0.1.0/tests/test_config_module.py +474 -0
- spltz_viur_testing-0.1.0/tests/test_package.py +341 -0
- spltz_viur_testing-0.1.0/tests/test_protection.py +30 -0
- spltz_viur_testing-0.1.0/tests/test_runner.py +255 -0
- spltz_viur_testing-0.1.0/tests/test_test_container.py +191 -0
- 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
|
+
[](https://github.com/sprengplatz/viur-testing/actions/workflows/test.yml)
|
|
61
|
+
[](https://sprengplatz.github.io/viur-testing/)
|
|
62
|
+
[](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
|
+
[](https://github.com/sprengplatz/viur-testing/actions/workflows/test.yml)
|
|
4
|
+
[](https://sprengplatz.github.io/viur-testing/)
|
|
5
|
+
[](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
|