Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
7dcfafa
RavenDB-27069 Add attach-to-external-server mode to the test driver
poissoncorp Jul 22, 2026
2f9270d
RavenDB-27069 CI: net10-only embedded job + Dockerized attach (path C…
poissoncorp Jul 22, 2026
f8617bd
RavenDB-27069 Require Python >=3.10 (ravendb client cannot import on …
poissoncorp Jul 22, 2026
ccbad20
RavenDB-27069 Add Lab 03: attach to a self-run server (Docker/testcon…
poissoncorp Jul 22, 2026
b50bee2
RavenDB-27069 Document .NET requirement, Python 3.10+, and the attach…
poissoncorp Jul 22, 2026
2b15bc2
RavenDB-27069 Fix stale .gitignore package name (ravendb_testdriver -…
poissoncorp Jul 23, 2026
c13437f
RavenDB-27069 Trim comments to critical one-liners; drop unused import
poissoncorp Jul 23, 2026
d024942
RavenDB-27069 Rewrite README around usage cases (why/how + link labs)
poissoncorp Jul 23, 2026
bd8e05d
RavenDB-27069 Bump to 7.2.5; pin deps (ravendb-embedded==7.2.3 publis…
poissoncorp Jul 23, 2026
7e1ed38
RavenDB-27069 Pin GitHub Actions to commit SHA; add Dependabot (actio…
poissoncorp Jul 23, 2026
63f2642
RavenDB-27069 Give test-driver its own lab numbering (attach = Lab 01)
poissoncorp Jul 23, 2026
025c92e
RavenDB-27069 Fix wait_for_indexing: it returned before stale indexes…
poissoncorp Jul 23, 2026
838e940
RavenDB-27069 Add Lab 02 (embedded per-test) and Lab 03 (seeding + in…
poissoncorp Jul 23, 2026
b4615ff
RavenDB-27069 Drop redundant requirements.txt (duplicated and drifted…
poissoncorp Jul 23, 2026
89c60e9
RavenDB-27069 Attach: support secured (https) servers via client cert…
poissoncorp Jul 28, 2026
9b59a57
RavenDB-27069 Pin ravendb-embedded 7.2.5 for release
poissoncorp Jul 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
github-actions:
patterns:
- "*"

- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
74 changes: 60 additions & 14 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,28 @@ on:
workflow_dispatch:

jobs:
test:
embedded:
# Path A: the driver boots the embedded server (a .NET app). The 7.2 server targets
# net10.0, so install .NET 10 explicitly (7.1 was net8.0). The attach test skips here
# (no RAVENDB_TEST_SERVER_URL).
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
# Latest Python only (the ravendb client requires 3.10+); covers ubuntu + windows.
matrix:
include:
- { os: ubuntu-latest, python: "3.13" }
- { os: windows-latest, python: "3.13" }
os: [ubuntu-latest, windows-latest]

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5
- name: Set up Python 3.13
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: ${{ matrix.python }}
python-version: "3.13"

# The test driver runs the embedded RavenDB server (a .NET application);
# the server binaries ship inside the ravendb-embedded dependency wheel.
- name: Set up .NET 8
uses: actions/setup-dotnet@v4
- name: Set up .NET 10
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
with:
dotnet-version: "8.0"
dotnet-version: "10.0"

- name: Install package
run: |
Expand All @@ -45,3 +43,51 @@ jobs:

- name: Run tests
run: python -m unittest discover -s tests

- name: Run Lab 02 (embedded per-test)
run: python labs/02_embedded_per_test.py

- name: Run Lab 03 (seeding + indexes)
run: python labs/03_seeding_indexes.py

attach:
# Path C: attach to a RavenDB server running in Docker, with NO .NET installed. Proves the
# driver needs no runtime when it does not boot the embedded server.
runs-on: ubuntu-latest
services:
ravendb:
image: ravendb/ravendb:7.2-ubuntu-latest
ports:
- 8080:8080
env:
RAVEN_Setup_Mode: None
RAVEN_License_Eula_Accepted: "true"
RAVEN_Security_UnsecuredAccessAllowed: PublicNetwork
RAVEN_ServerUrl: http://0.0.0.0:8080

steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Set up Python 3.13
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.13"

- name: Install package
run: |
python -m pip install --upgrade pip
pip install -e .

- name: Wait for RavenDB
run: curl --retry 60 --retry-delay 2 --retry-all-errors -sf http://localhost:8080/build/version

- name: Attach test (path C, no .NET)
env:
RAVENDB_TEST_SERVER_URL: http://localhost:8080
RAVENDB_TEST_REQUIRE_ATTACH: "1"
run: python -m unittest tests.test_attach -v

- name: Run Lab 01 (attach, no .NET)
env:
RAVENDB_TEST_SERVER_URL: http://localhost:8080
run: python labs/01_attach_to_server.py
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/.idea
/test.py
/dist
/ravendb_testdriver.egg-info
/ravendb_testdriver/target
/ravendb_test_driver.egg-info
/ravendb_test_driver/target
*.pyc
*.log
*.raven-topology
Expand Down
88 changes: 71 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,92 @@
## RavenDB Test Driver
# RavenDB Test Driver

`ravendb-test-driver` is a package for writing integration tests against RavenDB server.
`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.

### Setup
## Install

Install from PyPi:
```bash
pip install ravendb-test-driver
```

Python 3.10+ is required.

## Providing a server: pick one

The driver needs a RavenDB server to talk to. There are two ways to give it one; choose based on
whether you want .NET on the test machine.

### 1. Embedded server (default, needs .NET)

`pip install ravendb-test-driver`
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:

| `ravendb-test-driver` version | Required runtime |
|-------------------------------|------------------|
| 7.2.x | .NET 10 |
| 7.1.x | .NET 8 |

### Usage
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.

Inherit `RavenTestDriver` to your test class or create an instance within your class.
### 2. Attach to a server you run yourself (no .NET)

Unittest example:
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.

```python
from ravendb_test_driver import RavenTestDriver

RavenTestDriver.configure_external_server("http://localhost:8080")
# or set RAVENDB_TEST_SERVER_URL in the environment (handy for CI)
```

Call it 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). For the embedded and
self-contained server options, see the
[`ravendb-python-embedded`](https://github.com/ravendb/ravendb-python-embedded) repository.

## Usage

Subclass `RavenTestDriver` (or hold an instance) and call `get_document_store()` in each test to
get a store backed by a fresh database:

```python
from unittest import TestCase
from ravendb_test_driver import RavenTestDriver


class TestBasic(TestCase):
def setUp(self):
super().setUp()
self.test_driver = RavenTestDriver()

def test_1(self):
with self.test_driver.get_document_store() as store:
def test_stores_a_document(self):
with self.test_driver.get_document_store() as store: # isolated database
with store.open_session() as session:
person = {"Name": "John"}
session.store(person, "people1")
session.store({"Name": "John"}, "people/1")
session.save_changes()
```
### PyPi
https://pypi.org/project/ravendb-test-driver/

### Github
https://github.com/ravendb/ravendb-python-testdriver
Runnable example: [`labs/02-embedded-per-test.md`](labs/02-embedded-per-test.md).

### Seeding data and waiting for indexes

- 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 example: [`labs/03-seeding-indexes.md`](labs/03-seeding-indexes.md).

## 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
88 changes: 88 additions & 0 deletions labs/01-attach-to-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Lab 01: Attach to a server you run yourself

**For:** containerized CI, or anyone who does not want the driver to boot the embedded server
(and therefore wants no .NET on the machine). You run RavenDB yourself (Docker, testcontainers,
a shared CI service) and point the driver at its URL. The driver still creates a fresh, isolated
database per test and cleans it up afterwards.

## Point the driver at a server

Two equivalent ways:

```python
from ravendb_test_driver import RavenTestDriver

# 1. Explicit, before the first get_document_store():
RavenTestDriver.configure_external_server("http://localhost:8080")

# 2. Or set an environment variable (nice for CI):
# RAVENDB_TEST_SERVER_URL=http://localhost:8080
```

For a secured (https) server, pass the client certificate: `configure_external_server(url,
certificate_pem_path=...)` (or set `RAVENDB_TEST_SERVER_CERT`); attaching to https without one
fails fast with a clear message.

Then use the driver exactly as with the embedded server:

```python
with RavenTestDriver() as driver:
with driver.get_document_store() as store: # isolated database on the attached server
...
```

The complete runnable example is [`01_attach_to_server.py`](01_attach_to_server.py).

## Run a server with Docker

```bash
docker run -d -p 8080:8080 \
-e RAVEN_Setup_Mode=None -e RAVEN_License_Eula_Accepted=true \
-e RAVEN_Security_UnsecuredAccessAllowed=PublicNetwork -e RAVEN_ServerUrl=http://0.0.0.0:8080 \
ravendb/ravendb:7.2-ubuntu-latest

RAVENDB_TEST_SERVER_URL=http://localhost:8080 python labs/01_attach_to_server.py
```

## Run a server with testcontainers-python

There is no dedicated RavenDB module yet, so use the generic container:

```python
from testcontainers.core.container import DockerContainer
from testcontainers.core.waiting_utils import wait_for_logs

raven = (
DockerContainer("ravendb/ravendb:7.2-ubuntu-latest")
.with_env("RAVEN_Setup_Mode", "None")
.with_env("RAVEN_License_Eula_Accepted", "true")
.with_env("RAVEN_Security_UnsecuredAccessAllowed", "PublicNetwork")
.with_env("RAVEN_ServerUrl", "http://0.0.0.0:8080")
.with_exposed_ports(8080)
)
raven.start()
wait_for_logs(raven, "Server available on")
url = f"http://{raven.get_container_host_ip()}:{raven.get_exposed_port(8080)}"

from ravendb_test_driver import RavenTestDriver
RavenTestDriver.configure_external_server(url)
```

## In CI

This repo's own CI uses a GitHub Actions service container (see `.github/workflows/tests.yml`,
the `attach` job): it runs `ravendb/ravendb:7.2-ubuntu-latest`, waits for it, then runs the
attach test with `RAVENDB_TEST_SERVER_URL` set and no .NET installed.

## Cleanup on a shared server

Each test's database is deleted when its store closes, so dispose the driver (use it as a
context manager). If a run is hard-killed before that, per-test `test_*` databases can be left
behind on a shared server and a rerun may collide with them; prefer a fresh or per-run server,
and prune leftover `test_*` databases between runs.

## Takeaway

No embedded server and no .NET, at the cost of running RavenDB yourself. If you would rather
have the driver run the server for you (with or without .NET), see the embedded options in the
`ravendb-python-embedded` repository.
44 changes: 44 additions & 0 deletions labs/01_attach_to_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Lab 01: Attach to a server you run yourself (Docker / testcontainers / shared CI).

For: containerized CI, or any setup where you do NOT want the driver to boot the embedded
server (so you need no .NET). You run RavenDB yourself and point the driver at its URL; the
driver still gives every test its own isolated database.

Start a server, for example with Docker:
docker run -d -p 8080:8080 \
-e RAVEN_Setup_Mode=None -e RAVEN_License_Eula_Accepted=true \
-e RAVEN_Security_UnsecuredAccessAllowed=PublicNetwork -e RAVEN_ServerUrl=http://0.0.0.0:8080 \
ravendb/ravendb:7.2-ubuntu-latest

Then run:
RAVENDB_TEST_SERVER_URL=http://localhost:8080 python labs/01_attach_to_server.py
"""

import os
import sys

from ravendb_test_driver import RavenTestDriver

URL = os.environ.get("RAVENDB_TEST_SERVER_URL") or (sys.argv[1] if len(sys.argv) > 1 else None)
if not URL:
sys.exit("Set RAVENDB_TEST_SERVER_URL (or pass a URL) to a running RavenDB server. See the header.")


def main() -> None:
# Attach explicitly (equivalent to setting RAVENDB_TEST_SERVER_URL); call before the
# first get_document_store.
RavenTestDriver.configure_external_server(URL)

with RavenTestDriver() as driver:
with driver.get_document_store() as store: # a fresh, isolated database on the attached server
with store.open_session() as session:
session.store({"name": "Ayende"}, "people/1")
session.save_changes()
with store.open_session() as session:
assert session.load("people/1", dict)["name"] == "Ayende"

print("Lab 01 OK: attached to an external server, no embedded boot and no .NET.")


if __name__ == "__main__":
main()
Loading
Loading