From 65730b3319acbb70bb9d03a207fff68c22b418bf Mon Sep 17 00:00:00 2001 From: Gracjan Sadowicz Date: Tue, 28 Jul 2026 05:44:17 +0200 Subject: [PATCH 1/2] Improve README and cover secured attach --- README.md | 186 +++++++++++++++++++++++---------- tests/test_a_secured_attach.py | 14 +++ 2 files changed, 142 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index f8e4e41..66685ae 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # RavenDB Test Driver -`ravendb-test-driver` runs your integration tests against a **real** RavenDB server instead of a -mock. Each test gets its own isolated database, created on demand and torn down afterwards, so -tests do not leak state into one another. You write ordinary `ravendb` client code; the driver -handles the server and the per-test database lifecycle. +`ravendb-test-driver` runs integration tests against a real RavenDB server instead of a mock. It +creates an isolated database for each test and deletes that database when its `DocumentStore` is +closed. Your tests use the standard `ravendb` client API. ## Install @@ -13,29 +12,55 @@ pip install ravendb-test-driver Python 3.10+ is required. -## Providing a server: pick one +## Quick start -The driver needs a RavenDB server to talk to. Choose how it should get one based on your -environment. +With the default configuration, the driver starts an embedded RavenDB server and gives every +store its own database: -### 1. Embedded server (default, needs .NET) +```python +from ravendb_test_driver import RavenTestDriver + +with RavenTestDriver() as driver: + with driver.get_document_store() as store: + with store.open_session() as session: + session.store({"name": "John"}, "people/1") + session.save_changes() + + with store.open_session() as session: + assert session.load("people/1", dict)["name"] == "John" +``` + +The default mode requires a matching system .NET runtime. The two alternatives below do not +require .NET on the machine running the Python tests. + +## Choose where RavenDB runs + +| Mode | .NET on the test machine? | Who manages the server? | Best for | +|------|---------------------------|-------------------------|----------| +| [Embedded default](#embedded-server-default) | Yes | Test driver | The simplest local setup | +| [On-demand self-contained](#on-demand-self-contained-server) | No | Test driver | Portable developer machines and CI runners | +| [Attach to your server](#attach-to-a-server-you-run) | No | You | Docker, Testcontainers, or a shared service | -Out of the box the driver boots an **embedded** RavenDB server (via `ravendb-embedded`). Nothing -to configure, but the embedded server is a .NET application, so a matching runtime must be -installed: +Configure the selected mode before the first call to `get_document_store()`. + +### Embedded server (default) + +No configuration is needed. The driver starts the framework-dependent server bundled with +`ravendb-embedded`. | `ravendb-test-driver` version | Required runtime | |-------------------------------|------------------| | 7.2.x | .NET 10 | | 7.1.x | .NET 8 | -Check with `dotnet --list-runtimes`. The requirement tracks the embedded server and can change on -a minor upgrade, so re-check it when you bump versions. +Run `dotnet --list-runtimes` and look for `Microsoft.NETCore.App`. Re-check the requirement when +upgrading to a new RavenDB minor version. -### 2. Self-contained embedded server (no system .NET) +Runnable walkthrough: [Lab 02 — isolated embedded databases](labs/02-embedded-per-test.md). -Let the driver download and cache a self-contained RavenDB build. It manages the server for you -without calling `dotnet`: +### On-demand self-contained server + +Let the driver download, cache, and manage the self-contained build for the current platform: ```python from ravendb_embedded import ServerOptions @@ -44,83 +69,128 @@ from ravendb_test_driver import RavenTestDriver options = ServerOptions() options.with_auto_downloaded_server() RavenTestDriver.configure_server(options) + +with RavenTestDriver() as driver: + with driver.get_document_store() as store: + ... ``` -The embedded package detects the host operating system and architecture, so the same test -configuration is portable across supported Windows, Linux, and macOS machines. +The same test configuration works across supported Windows, Linux, and macOS machines because the +operating system and architecture are detected at runtime. The first run downloads 100 MB+; +later runs reuse `~/.cache/ravendb-embedded`. Pass `cache_root` to +`with_auto_downloaded_server()` when your build system restores a different cache directory. -Call `configure_server()` before the first `get_document_store()`. The first run downloads -100 MB+; later runs reuse the cache. Self-contained mode needs no system .NET, but normal RavenDB -OS dependencies still apply; minimal Linux images may need their distribution's ICU package. See -[`labs/04-embedded-no-dotnet.md`](labs/04-embedded-no-dotnet.md). +Supported targets are Windows x64/x86, Linux x64/ARM64, and macOS x64/ARM64. Self-contained mode +removes the system .NET requirement, but normal RavenDB operating-system dependencies still +apply. Minimal Linux images may need their distribution's ICU package. The Python wheel stays +platform-independent because it downloads only the self-contained build needed by the current +machine rather than bundling every platform. -### 3. Attach to a server you run yourself (no .NET) +Runnable walkthrough: [Lab 04 — portable embedded tests without .NET](labs/04-embedded-no-dotnet.md). -If you would rather not put .NET on the test machine (containerized CI, locked-down hosts), run -RavenDB yourself (Docker, testcontainers, a shared CI service) and point the driver at its URL. -The driver skips the embedded boot entirely and still creates an isolated database per test. +### Attach to a server you run + +Start RavenDB yourself—locally, in Docker or Testcontainers, or as a shared service—and configure +its URL: ```python from ravendb_test_driver import RavenTestDriver RavenTestDriver.configure_external_server("http://localhost:8080") -# or set RAVENDB_TEST_SERVER_URL in the environment ``` +Alternatively, configure the URL through the environment: + +```bash +RAVENDB_TEST_SERVER_URL=http://localhost:8080 python -m unittest +``` + +This path does not use `EmbeddedServer`: the driver neither starts nor stops the server, but it +still creates and deletes an isolated database for each test. No .NET installation is needed on +the test machine; the server environment supplies its own runtime. + For HTTPS with client-certificate authentication: ```python RavenTestDriver.configure_external_server( "https://my-ravendb", certificate_pem_path="client.pem", - trust_store_path="ca.crt", # needed when the server CA is not already trusted + trust_store_path="ca.crt", ) ``` -The equivalent environment variables are `RAVENDB_TEST_SERVER_URL`, -`RAVENDB_TEST_SERVER_CERT`, and `RAVENDB_TEST_SERVER_CA`. Call the configuration method once, -before the first `get_document_store()`. A runnable Docker / testcontainers guide is in -[`labs/01-attach-to-server.md`](labs/01-attach-to-server.md). +The equivalent environment variables are: + +- `RAVENDB_TEST_SERVER_URL` +- `RAVENDB_TEST_SERVER_CERT` +- `RAVENDB_TEST_SERVER_CA` -## Usage +`trust_store_path` or `RAVENDB_TEST_SERVER_CA` is needed when the server's CA is not already +trusted by the test machine. -Subclass `RavenTestDriver` (or hold an instance) and call `get_document_store()` in each test to -get a store backed by a fresh database: +Runnable walkthrough: [Lab 01 — Docker, Testcontainers, and shared servers](labs/01-attach-to-server.md). + +## Test lifecycle + +Create a `RavenTestDriver` for the test or fixture, then close every returned store. A context +manager handles both steps: ```python from unittest import TestCase from ravendb_test_driver import RavenTestDriver -class TestBasic(TestCase): - def setUp(self): - self.test_driver = RavenTestDriver() +class TestPeople(TestCase): + def test_stores_a_person(self): + with RavenTestDriver() as driver: + with driver.get_document_store() as store: + with store.open_session() as session: + session.store({"name": "John"}, "people/1") + session.save_changes() +``` + +Each `get_document_store()` call creates a new database. Closing the store deletes it, which keeps +tests independent even when they share one RavenDB server process. - def test_stores_a_document(self): - with self.test_driver.get_document_store() as store: # isolated database - with store.open_session() as session: - session.store({"Name": "John"}, "people/1") - session.save_changes() +## Seed data and wait for indexing + +Override `setup_database(self, store)` to create indexes or seed reference data whenever the +driver creates a database: + +```python +class PeopleTestDriver(RavenTestDriver): + def setup_database(self, store): + with store.open_session() as session: + session.store({"name": "Seeded"}, "people/seed") + session.save_changes() ``` -Runnable example: [`labs/02-embedded-per-test.md`](labs/02-embedded-per-test.md). +Use `GetDocumentStoreOptions.wait_for_indexing_timeout` when a store should not be returned until +indexing settles, or call `wait_for_indexing(store)` directly. -### Seeding data and waiting for indexes +`wait_for_user_to_continue_the_test(store)` opens RavenDB Studio and pauses the test for manual +inspection. -- Override `setup_database(self, store)` to seed or configure every database the driver hands - out (indexes, reference data, and so on). -- `get_document_store(options)` accepts `GetDocumentStoreOptions`; set a - `wait_for_indexing_timeout` to block until indexing settles, or call - `wait_for_indexing(store)` yourself. -- `wait_for_user_to_continue_the_test(store)` opens RavenDB Studio so you can inspect the data - mid-test. +Runnable walkthrough: [Lab 03 — seeding and indexes](labs/03-seeding-indexes.md). -Runnable example: [`labs/03-seeding-indexes.md`](labs/03-seeding-indexes.md). +## Labs -## Links +| Lab | Scenario | Needs system .NET? | +|-----|----------|--------------------| +| [01](labs/01-attach-to-server.md) | Attach to Docker, Testcontainers, or a shared server | No | +| [02](labs/02-embedded-per-test.md) | Default embedded server and isolated databases | Yes | +| [03](labs/03-seeding-indexes.md) | Seed data and wait for real indexing | Yes | +| [04](labs/04-embedded-no-dotnet.md) | On-demand self-contained server | No | -The runnable lab scripts live in this repository and are not installed into `site-packages`. +The runnable scripts live in this repository rather than `site-packages`. Clone or download the +repository, install the package, and run them from the repository root. See the +[complete labs guide](labs/README.md). + +For lower-level server configuration, see +[`ravendb-embedded`](https://github.com/ravendb/ravendb-python-embedded). + +## Links -- PyPI: https://pypi.org/project/ravendb-test-driver/ -- GitHub: https://github.com/ravendb/ravendb-python-testdriver -- Server and self-contained options: https://github.com/ravendb/ravendb-python-embedded +- [PyPI](https://pypi.org/project/ravendb-test-driver/) +- [Source](https://github.com/ravendb/ravendb-python-testdriver) +- [RavenDB Python client documentation](https://ravendb.net/docs/article-page/latest/python) diff --git a/tests/test_a_secured_attach.py b/tests/test_a_secured_attach.py index 70ec0a8..4ea90c3 100644 --- a/tests/test_a_secured_attach.py +++ b/tests/test_a_secured_attach.py @@ -11,6 +11,7 @@ from cryptography.hazmat.primitives.serialization import pkcs12 from cryptography.x509.oid import ExtendedKeyUsageOID, NameOID from ravendb import Lazy +from ravendb.exceptions.raven_exceptions import RavenException from ravendb_embedded import EmbeddedServer, ServerOptions from ravendb_test_driver import RavenTestDriver @@ -140,3 +141,16 @@ def test_api_and_environment_credentials_reach_database_store(self): for name, value in original_environment.items(): if value is not None: os.environ[name] = value + + def test_https_attach_requires_a_client_certificate(self): + original_environment = {name: os.environ.get(name) for name in self._ENV_NAMES} + self._reset_driver() + try: + RavenTestDriver.configure_external_server("https://127.0.0.1:1") + with self.assertRaisesRegex(RavenException, "needs a client certificate"): + RavenTestDriver.run_server() + finally: + self._reset_driver() + for name, value in original_environment.items(): + if value is not None: + os.environ[name] = value From cc720b1de8f43b95b265345fb67c85fcfec23661 Mon Sep 17 00:00:00 2001 From: Gracjan Sadowicz Date: Thu, 30 Jul 2026 11:30:31 +0200 Subject: [PATCH 2/2] RavenDB-27141 Prepare 7.2.5.post2 release --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 10de8d0..4a97f94 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name="ravendb-test-driver", packages=find_packages(exclude=["*.tests.*", "tests", "*.tests", "tests.*"]), - version="7.2.5.post1", + version="7.2.5.post2", description="RavenDB package for writing integration tests against RavenDB server", long_description_content_type="text/markdown", long_description=open("README.md").read(), @@ -14,5 +14,5 @@ keywords=["ravendb", "nosql", "database", "test", "driver"], python_requires=">=3.10", license_files=["LICENSE"], - install_requires=["ravendb-embedded==7.2.5.post1", "ravendb==7.2.3.post1"], + install_requires=["ravendb-embedded==7.2.5.post2", "ravendb==7.2.3.post1"], )