diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b8a5b00 --- /dev/null +++ b/.github/dependabot.yml @@ -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" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b1ff8b5..cbd548f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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: | @@ -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 diff --git a/.gitignore b/.gitignore index 3a67088..db43a53 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md index 2427285..d9044f7 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/labs/01-attach-to-server.md b/labs/01-attach-to-server.md new file mode 100644 index 0000000..64a7953 --- /dev/null +++ b/labs/01-attach-to-server.md @@ -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. diff --git a/labs/01_attach_to_server.py b/labs/01_attach_to_server.py new file mode 100644 index 0000000..3157822 --- /dev/null +++ b/labs/01_attach_to_server.py @@ -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() diff --git a/labs/02-embedded-per-test.md b/labs/02-embedded-per-test.md new file mode 100644 index 0000000..3a7f6ef --- /dev/null +++ b/labs/02-embedded-per-test.md @@ -0,0 +1,38 @@ +# Lab 02: Embedded server, one isolated database per test (the default) + +**For:** the normal way to write tests with the driver. Subclass `RavenTestDriver` (or hold an +instance); each `get_document_store()` returns a store backed by a fresh, isolated database that +is cleaned up afterwards, so tests never see each other's data. This path boots the embedded +server and needs a matching .NET (see the README). + +## Run it + +```bash +pip install ravendb-test-driver +python labs/02_embedded_per_test.py +``` + +The complete example is [`02_embedded_per_test.py`](02_embedded_per_test.py). In a real test: + +```python +from unittest import TestCase +from ravendb_test_driver import RavenTestDriver + +class TestThings(TestCase): + def setUp(self): + self.driver = RavenTestDriver() + + def test_it(self): + with self.driver.get_document_store() as store: # fresh isolated database + with store.open_session() as session: + session.store({"name": "John"}, "people/1") + session.save_changes() +``` + +Two `get_document_store()` calls give two different databases, so data written to one is invisible +to the other. That isolation is what keeps tests independent. + +## Takeaway + +No server to manage in your tests: the driver runs one and gives each test its own database. To +run without .NET, attach to a server you start yourself (Lab 01). diff --git a/labs/02_embedded_per_test.py b/labs/02_embedded_per_test.py new file mode 100644 index 0000000..e13b8f1 --- /dev/null +++ b/labs/02_embedded_per_test.py @@ -0,0 +1,31 @@ +"""Lab 02: Embedded server, one isolated database per test (the default). + +For: the normal way to write tests with the driver. Subclass RavenTestDriver (or hold an +instance); each get_document_store() returns a store backed by a fresh, isolated database that is +cleaned up afterwards, so tests never see each other's data. This path boots the embedded server +and therefore needs a matching .NET (see the README). + +Run: python labs/02_embedded_per_test.py +""" + +from ravendb_test_driver import RavenTestDriver + + +def main() -> None: + with RavenTestDriver() as driver: + with driver.get_document_store() as first, driver.get_document_store() as second: + assert first.database != second.database, (first.database, second.database) + + with first.open_session() as session: + session.store({"name": "only in first"}, "people/1") + session.save_changes() + + # The second store is a different database, so it cannot see the first store's data. + with second.open_session() as session: + assert session.load("people/1", dict) is None + + print("Lab 02 OK: two stores, two isolated databases, no cross-test leakage.") + + +if __name__ == "__main__": + main() diff --git a/labs/03-seeding-indexes.md b/labs/03-seeding-indexes.md new file mode 100644 index 0000000..d98f085 --- /dev/null +++ b/labs/03-seeding-indexes.md @@ -0,0 +1,49 @@ +# Lab 03: Seed data and query an index in tests + +**For:** tests that need pre-seeded data and a defined index, and must wait for indexing to settle +before asserting. Override `setup_database()` to seed and create the index for every database the +driver hands out; call `wait_for_indexing()` before querying so the assertion is not racing the +indexer. + +## Run it + +```bash +pip install ravendb-test-driver +python labs/03_seeding_indexes.py +``` + +The complete example is [`03_seeding_indexes.py`](03_seeding_indexes.py). The core is: + +```python +from ravendb.documents.indexes.abstract_index_creation_tasks import AbstractIndexCreationTask +from ravendb_test_driver import RavenTestDriver + +class People_ByName(AbstractIndexCreationTask): + def __init__(self): + super().__init__() + self.map = "from p in docs.People select new { p.name }" + +class SeedingDriver(RavenTestDriver): + def setup_database(self, store): # runs for every database the driver creates + store.execute_index(People_ByName()) + with store.open_session() as session: + session.store(Person(name="Seeded"), "people/1") + session.save_changes() + +with SeedingDriver() as driver: + with driver.get_document_store() as store: + driver.wait_for_indexing(store) # block until no index is stale + with store.open_session() as session: + hits = list(session.query_index_type(People_ByName, Person).where_equals("name", "Seeded")) +``` + +## Why `wait_for_indexing` + +RavenDB indexes are updated asynchronously, so right after you write, an index query can return +stale (empty) results. `wait_for_indexing()` blocks until no index is stale, making index-backed +assertions deterministic instead of flaky. + +## Takeaway + +`setup_database()` is the single place to seed data and register indexes for every test database; +`wait_for_indexing()` removes the race between writing and querying an index. diff --git a/labs/03_seeding_indexes.py b/labs/03_seeding_indexes.py new file mode 100644 index 0000000..adf7527 --- /dev/null +++ b/labs/03_seeding_indexes.py @@ -0,0 +1,48 @@ +"""Lab 03: Seed data and query an index in tests. + +For: tests that need pre-seeded data and a defined index, and must wait for indexing to settle +before asserting. Override setup_database() to seed and create the index for every database the +driver hands out; call wait_for_indexing() before querying so the assertion is not racing the +indexer. Boots the embedded server (needs .NET). + +Run: python labs/03_seeding_indexes.py +""" + +from ravendb.documents.indexes.abstract_index_creation_tasks import AbstractIndexCreationTask + +from ravendb_test_driver import RavenTestDriver + + +class Person: + def __init__(self, Id=None, name=None): + self.Id = Id + self.name = name + + +class People_ByName(AbstractIndexCreationTask): + def __init__(self): + super().__init__() + self.map = "from p in docs.People select new { p.name }" + + +class SeedingDriver(RavenTestDriver): + def setup_database(self, store) -> None: # runs for every database the driver creates + store.execute_index(People_ByName()) + with store.open_session() as session: + session.store(Person(name="Seeded"), "people/1") + session.save_changes() + + +def main() -> None: + with SeedingDriver() as driver: + with driver.get_document_store() as store: + driver.wait_for_indexing(store) # block until the index is no longer stale + with store.open_session() as session: + hits = list(session.query_index_type(People_ByName, Person).where_equals("name", "Seeded")) + assert len(hits) == 1 and hits[0].name == "Seeded", hits + + print("Lab 03 OK: setup_database seeded data + index, wait_for_indexing settled, query returned it.") + + +if __name__ == "__main__": + main() diff --git a/labs/README.md b/labs/README.md new file mode 100644 index 0000000..f6eada6 --- /dev/null +++ b/labs/README.md @@ -0,0 +1,14 @@ +# ravendb-test-driver: labs + +Runnable, self-checking guides for using the test driver. Each lab ships a script next to it, so +you can run the exact code the guide shows. + +| Lab | Covers | Needs system .NET? | +|-----|--------|--------------------| +| [01](01-attach-to-server.md) | Attach to a server you run yourself (Docker, testcontainers, shared CI) | No | +| [02](02-embedded-per-test.md) | Embedded server, one isolated database per test (the default) | Yes | +| [03](03-seeding-indexes.md) | Seed data and query an index (`setup_database`, `wait_for_indexing`) | Yes | + +Looking for how to run RavenDB itself (embedded, or self-contained without .NET)? That belongs to +the [`ravendb-python-embedded`](https://github.com/ravendb/ravendb-python-embedded) package and +has its own labs there. diff --git a/ravendb_test_driver/raven_test_driver.py b/ravendb_test_driver/raven_test_driver.py index b78d36f..dbe8cd2 100644 --- a/ravendb_test_driver/raven_test_driver.py +++ b/ravendb_test_driver/raven_test_driver.py @@ -25,7 +25,6 @@ from ravendb.serverwide.database_record import DatabaseRecord from ravendb.serverwide.operations.common import DeleteDatabaseOperation from ravendb_embedded import EmbeddedServer, ServerOptions -from ravendb_embedded.raven_server_runner import CommandLineArgumentEscaper from ravendb_test_driver.options import GetDocumentStoreOptions @@ -36,6 +35,8 @@ class RavenTestDriver: _INDEX = 0 _GLOBAL_SERVER_OPTIONS: Optional[ServerOptions] = None _EMPTY_SETTINGS_FILE_NAME: Optional[str] = None + _EXTERNAL_SERVER_URL: Optional[str] = None + _EXTERNAL_SERVER_CERT: Optional[str] = None def __init__(self) -> None: self.disposed = False @@ -66,6 +67,21 @@ def configure_server(options: ServerOptions) -> None: ) RavenTestDriver._GLOBAL_SERVER_OPTIONS = options + @staticmethod + def configure_external_server(url: str, certificate_pem_path: str = None) -> None: + """Attach to a server you run yourself (no embedded boot, no .NET); still one database per test. + + For a secured (https) server, pass the client certificate `.pem`. Equivalent to setting + RAVENDB_TEST_SERVER_URL (and RAVENDB_TEST_SERVER_CERT). Call before the first get_document_store. + """ + if RavenTestDriver._TEST_SERVER_STORE.is_value_created: + raise RuntimeError( + "Cannot configure the server after it was started. " + "Call 'configure_external_server' before any 'get_document_store'." + ) + RavenTestDriver._EXTERNAL_SERVER_URL = url + RavenTestDriver._EXTERNAL_SERVER_CERT = certificate_pem_path + def get_document_store( self, options: Optional[GetDocumentStoreOptions] = None, @@ -127,15 +143,15 @@ def wait_for_indexing( while time.monotonic() - start_time < timeout.total_seconds(): database_statistics = admin.send(GetStatisticsOperation()) - indexes = [ + stale = [ x for x in database_statistics.indexes if x.state != IndexState.DISABLED - and not x.stale + and x.stale and not x.name.startswith(Documents.Indexing.SIDE_BY_SIDE_INDEX_NAME_PREFIX) ] - if all(indexes): + if not stale: return if any(index.state == IndexState.ERROR for index in database_statistics.indexes): @@ -232,16 +248,28 @@ def cleanup_temp_dirs() -> None: @classmethod def run_server(cls) -> DocumentStore: + external_url = cls._EXTERNAL_SERVER_URL or os.environ.get("RAVENDB_TEST_SERVER_URL") + if external_url: + # Attach to an existing server; do not boot the embedded one (no .NET needed). + certificate = cls._EXTERNAL_SERVER_CERT or os.environ.get("RAVENDB_TEST_SERVER_CERT") + if external_url.lower().startswith("https") and not certificate: + raise RavenException( + f"Attaching to a secured server ({external_url}) needs a client certificate; pass " + "configure_external_server(url, certificate_pem_path=...) or set RAVENDB_TEST_SERVER_CERT." + ) + store = DocumentStore(external_url, None) + if certificate: + store.certificate_pem_path = certificate + store.initialize() + return store + try: options = RavenTestDriver._GLOBAL_SERVER_OPTIONS or RavenTestDriver.default_server_options() command_line_args = options.command_line_args command_line_args.insert(0, "-c") - command_line_args.insert( - 1, - CommandLineArgumentEscaper.escape_single_arg(RavenTestDriver._get_empty_settings_file()), - ) + command_line_args.insert(1, RavenTestDriver._get_empty_settings_file()) except Exception as e: raise RavenException(f"Unable to start server: {e}") diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 6d227a9..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -ravendb~=7.2.3 -ravendb-embedded==7.2.3 -setuptools~=68.0.0 \ No newline at end of file diff --git a/setup.py b/setup.py index 3923ff2..e1629ef 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.3", + version="7.2.5", description="RavenDB package for writing integration tests against RavenDB server", long_description_content_type="text/markdown", long_description=open("README.md").read(), @@ -12,7 +12,7 @@ url="https://github.com/ravendb/ravendb-python-testdriver", license="MIT", keywords=["ravendb", "nosql", "database", "test", "driver"], - python_requires="~=3.9", + python_requires=">=3.10", license_files="LICENSE", - install_requires=["ravendb-embedded==7.2.3", "ravendb~=7.2.3"], + install_requires=["ravendb-embedded==7.2.5", "ravendb==7.2.3.post1"], ) diff --git a/tests/test_attach.py b/tests/test_attach.py new file mode 100644 index 0000000..c1ac567 --- /dev/null +++ b/tests/test_attach.py @@ -0,0 +1,31 @@ +"""Attach path (no embedded boot, no .NET): point RAVENDB_TEST_SERVER_URL at a running server. + +Skips when unset, unless RAVENDB_TEST_REQUIRE_ATTACH=1 (CI) makes a missing URL fail loudly. +""" + +import os +from unittest import TestCase + +from ravendb_test_driver import RavenTestDriver + +SERVER_URL = os.environ.get("RAVENDB_TEST_SERVER_URL") +_REQUIRE = os.environ.get("RAVENDB_TEST_REQUIRE_ATTACH") == "1" + + +class TestAttachToExternalServer(TestCase): + def setUp(self): + if not SERVER_URL: + message = "set RAVENDB_TEST_SERVER_URL to a running server to run the attach test" + if _REQUIRE: + self.fail(message) + self.skipTest(message) + + def test_attach_gives_isolated_database(self): + driver = RavenTestDriver() + with driver.get_document_store() as store: + self.assertIn(SERVER_URL.rstrip("/"), store.urls[0]) + with store.open_session() as session: + session.store({"name": "attached"}, "people/1") + session.save_changes() + with store.open_session() as session: + self.assertEqual("attached", session.load("people/1", dict)["name"])