Skip to main content

viur-testing

Tests Docs License: MIT

Safe test-mode for ViUR core projects — primarily for Playwright end-to-end tests. Swaps the default datastore database out for a dedicated named database (default: viur-tests), with a bilateral handshake that refuses to let any test or endpoint run unless both the server and the runner agree they are talking to the test instance.

Bilateral guarantee in six lines

  1. activate() refuses outside conf.instance.is_dev_server — read from viur-core's canonical flag.
  2. Synchronous datastore probe in the target database has to succeed before the client swap is applied.
  3. TestModule and ConfigModule both refuse to instantiate outside the dev server or without a prior activate() call — both __init__s raise in either case. Even a host that bypasses the TestModule container and mounts ConfigModule directly is subject to the same checks, so a forgotten activate or a stray production mount fails loudly at boot.
  4. Per-request token validator (viur-test-token cookie) blocks every request that does not carry the session token, except the bootstrap endpoints.
  5. protect() installs a production-side cookie guard that 403s any request carrying the viur-test-token cookie outside dev, regardless of its value. Installed in every environment.
  6. Runner preflight calls /json/_test/config/status and refuses to run any test if the server's reply (database, project_id, token hash) does not match.

The session token is the source of truth in the test database itself (kind viur-tests, entity auth-token).

The runner side does cache a copy: the Playwright globalSetup persists the parsed /_test/config/status response under .auth/token.json (plus a process.env.E2E_TEST_TOKEN for spawned subprocesses) so that worker processes and per-test fixtures can read the session info without re-hitting the bootstrap endpoint. .auth/ is gitignored by the viur-testing-init template; globalTeardown deletes the file on suite end. If you want to drop both the on-disk and env-var copies in your own setup, replace createGlobalSetup/createGlobalTeardown with a custom wrapper that uses your preferred mechanism instead.

Module layout

/_test/                    TestModule (container, refuses outside dev mode)
  /_test/config/status     ConfigModule.status — issues/returns token
  /_test/config/enter      ConfigModule.enter  — sets the token cookie
  /_test/config/finish     ConfigModule.finish — deletes token entity

Future test flavours (load test, integration helpers, …) go in as sibling submodules under the same _test container.

Requirements

  • Python ≥ 3.12
  • viur-core ≥ 3.7, < 4
  • A named Datastore database (default name: viur-tests) created in your GCP project alongside (default).

Install

The PyPI distribution name is spltz-viur-testing (experimental prefix); the Python import path stays viur.testing:

pip install spltz-viur-testing
import viur.testing
viur.testing.setup()

Server-side wiring

Two one-liners. main.py — as the first lines, before any viur.core import:

import viur.testing
viur.testing.setup()

# Only now may viur.core be imported by your own code.
from viur.core import setup as core_setup
import modules
import render

core_setup(modules, render)

viur.testing.setup() reads the VIUR_TESTING env var (1/true/on for the default namespace, or any other value as the namespace verbatim); when on it calls activate() (datastore client swap + key-factory patch + closed-system allowlist + state priming + validator install) and always installs the production cookie guard via protect().

In modules/__init__.py register the test endpoints — idempotent and safe to leave in place for production deployments (no-op when test mode isn't armed):

# modules/__init__.py
import viur.testing
viur.testing.register_modules(globals())

That exposes POST /json/_test/config/status and POST /json/_test/config/finish. Both endpoints re-verify conf.instance.is_dev_server and the active datastore database before performing any work — defence in depth on top of the TestModule.__init__ guard.

If you need more control, the two functions wrap underlying primitives you can call yourself: viur.testing.activate(database=...), viur.testing.protect(), plus direct mounting via from viur.testing._test import TestModule.

Running the dev server with test mode

Toggle test mode at boot by setting the env var that setup() reads. 1 (or true/on) means on with the default namespace; any other value is the namespace verbatim (VIUR_TESTING=alice); unset / 0 / off means off:

VIUR_TESTING=1 viur run

Without the env var (or with off), setup() skips activate() and the process boots against the default database as if the package were not installed.

When test mode is active, the dev-server boot banner gains two extra lines — database = … and namespace = … — so the running slice is visible at a glance. The namespace line is rendered unconditionally; without a namespace in VIUR_TESTING it falls back to (default), making it obvious that test mode is armed but namespace isolation is not in effect:

# With VIUR_TESTING=alice
################## LOCAL DEVELOPMENT SERVER IS UP AND RUNNING ##################
#                          project = my-viur-project                           #
#                               python = 3.13.0                                #
#                                viur = 3.8.25                                 #
#                            database = viur-tests                             #
#                              namespace = alice                               #
################################################################################

# With VIUR_TESTING=1 (no namespace)
################## LOCAL DEVELOPMENT SERVER IS UP AND RUNNING ##################
#                          project = my-viur-project                           #
#                               python = 3.13.0                                #
#                                viur = 3.8.25                                 #
#                            database = viur-tests                             #
#                            namespace = (default)                             #
################################################################################

Concurrency: sharing one test database across multiple testers

The viur-tests database is a shared GCP resource. If two engineers both boot a dev server with the same database and run tests at the same time, their entities will collide — Person A's seed wipes Person B's user, Person B's test queries find leftovers from Person A.

The fix is the optional namespace argument. ViUR-testing passes it to google.cloud.datastore.Client(database=…, namespace=…) and rewires viur.core.db.Key so every read and write in the process is scoped to that namespace. Different namespaces in the same database are fully isolated — no separate DB provisioning needed.

Boot each dev server with its own namespace:

# Alice's machine
VIUR_TESTING=alice viur run

# Bob's machine
VIUR_TESTING=bob viur run

# CI for PR #42
VIUR_TESTING=ci-pr-42 viur run

The runner-side require_test_mode can assert the expected namespace to fail fast when somebody points at the wrong slice::

from viur.testing import require_test_mode

status = require_test_mode(
    "http://localhost:8080",
    expected_namespace="alice",  # omit to skip; pass None for default
)

VIUR_TESTING=1 (or true/on) means "on, no namespace — use the Datastore default", the right choice for a single-developer setup. Any other value is the namespace verbatim (VIUR_TESTING=ak → namespace ak); there is no separator and no reserved names.

Guarded Mode (Playwright only, since @spltz/viur-testing 0.3.0)

For occasional smoke tests on a deployed instance — landing pages, public catalog browsing, public marketing pages — spinning up a dedicated test backend is overkill. The Playwright companion package auto-detects this case: when the backend does not expose /_test/config/status (HTTP 404), it falls into Guarded Mode — no test database, no token cookie, no _test/ endpoints. Tests run as a normal browser would.

Because this bypasses the bilateral guarantee, Guarded Mode demands a fresh 6-digit PIN confirmation on the terminal every single run. No persisted ACK, no env-var bypass; no TTY → no run. Specs that depend on _test/ infrastructure (serverStatus, backendApi, callTestModule) are auto-skipped in Guarded Mode — only specs that act as a regular browser actually execute.

See playwright/README.md for the auto-detect table and the full run-mode behaviour.

Runner-side wiring (Playwright + npm)

The e2e runner ships as the npm package @spltz/viur-testing in this same repo (playwright/). Scaffold a working suite next to your project with:

npx viur-testing-init

The CLI asks interactively for the scaffold mode (Test Mode for a local dev server armed with VIUR_TESTING=1, Guarded Mode for tests against an already-deployed instance) and drops a working package.json, tsconfig.json, playwright.config.ts, vite.e2e.config.ts, .env.e2e, .gitignore and an example spec. To skip the prompt, pass --mode test|guarded or --guarded.

In the scaffolded directory:

cd testing/e2e
npm install
npx playwright install --with-deps chromium
E2E_BACKEND_URL=http://localhost:8080 npm test

The generated playwright.config.ts wires createGlobalSetup() / createGlobalTeardown() which probe /_test/config/status and pick the mode automatically. In Test Mode the fixtures set the viur-test-token cookie on the browser context and APIRequestContext; in Guarded Mode a 6-digit PIN gate appears on the terminal and specs that depend on /_test/ infrastructure auto-skip.

The Python-side primitives — require_test_mode, finish, ServerStatus, TestModePreflightError — are still available for hosts that drive their own runners (Python smoke harness, custom CI helpers); see the Runner API docs.

Naming

The Python package keeps its repository name viur-testing for stability, but everything inside it speaks the generic test vocabulary so future test flavours can be added under the same TestModule umbrella without churn. The _test URL prefix's leading underscore signals "system-internal, not for production callers".

Development

git clone git@github.com:sprengplatz/viur-testing.git
cd viur-testing
pip install -e ".[dev]"
pytest                  # 100% coverage required
mkdocs serve            # docs at http://localhost:8000

Documentation

See sprengplatz.github.io/viur-testing.

Changelog

See CHANGELOG.md.

License

MIT — see LICENSE.

Metadata

Release files for spltz-viur-testing 0.5.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for spltz-viur-testing 0.5.2
File Size Uploaded
spltz_viur_testing-0.5.2.tar.gz 72.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for spltz-viur-testing 0.5.2
File Interpreter ABI Platform
spltz_viur_testing-0.5.2-py3-none-any.whl Python 3 none any Details

Total release size: 123.1 kB

Release files / spltz_viur_testing-0.5.2.tar.gz

Download URL spltz_viur_testing-0.5.2.tar.gz
Size 72.2 kB
Tags Source
SHA-256 checksum
How to use checksums
59ba1976733b4a8efe2366d5dac3a31e6a8325b338d866bf08ab04d7f5e3a9b3
BLAKE2b-256 checksum
How to use checksums
4a4a681a1b148934feccbf4af5210656d5b3101ed4c2973827cd37a41819fd6e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.

Transparency log

Release files / spltz_viur_testing-0.5.2-py3-none-any.whl

Download URL spltz_viur_testing-0.5.2-py3-none-any.whl
Size 51.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
38b29833af9a8178f8c2df6f96eb0bc1367d90c1860f4866a337926ac49fd862
BLAKE2b-256 checksum
How to use checksums
5e68359372ffe7f6ba36437226de1ffefd1805588bd46241f2a19d341a5dc2e0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.2 This release

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page