Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions integration/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Fixtures for the integration suite."""

import os

import pytest


@pytest.fixture(scope="session")
def base_url():
"""Base URL of the running artifact, injected by `rbs integration-test`."""
url = os.environ.get("RBS_BASE_URL")
if not url:
pytest.fail("RBS_BASE_URL is unset; run this suite via `rbs integration-test`")
return url.rstrip("/")
55 changes: 55 additions & 0 deletions integration/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""Integration tests: the built image answering over HTTP.

Nothing here imports quantumsolver. Unlike tests/, which drives the app in-process via Flask's
test client, this suite only knows a URL — so it exercises the wheel, gunicorn, and the container
the way Redux does.
"""

import pytest
import requests

# Generous: each solve spawns a process that re-imports qiskit, and the app self-limits at 25s.
TIMEOUT = 40

# ([f0, f1], expected classification)
DEUTSCH_CASES = [
([0, 0], "constant"),
([0, 1], "balanced"),
]

# (f_bits, expected hidden string)
BERNSTEIN_VAZIRANI_CASES = [
([0, 1, 0, 1, 1, 0, 1, 0], "101"),
]


def test_health(base_url):
"""GET /health reports ok — the same route rbs polls for readiness."""
resp = requests.get(f"{base_url}/health", timeout=TIMEOUT)
assert resp.status_code == 200
assert resp.json()["status"] == "ok"


@pytest.mark.parametrize("path", ["/", "/index"])
def test_landing_routes(base_url, path):
"""The landing routes answer."""
resp = requests.get(f"{base_url}{path}", timeout=TIMEOUT)
assert resp.status_code == 200


@pytest.mark.parametrize("f,expected", DEUTSCH_CASES)
def test_deutsch(base_url, f, expected):
"""POST /deutsch-quantum classifies the oracle as constant or balanced."""
resp = requests.post(f"{base_url}/deutsch-quantum", json=f, timeout=TIMEOUT)
assert resp.status_code == 200
assert resp.json()["answer"] == expected


@pytest.mark.parametrize("f,expected", BERNSTEIN_VAZIRANI_CASES)
def test_bernstein_vazirani(base_url, f, expected):
"""POST /bernstein-vazirani-quantum recovers the hidden string."""
resp = requests.post(
f"{base_url}/bernstein-vazirani-quantum", json=f, timeout=TIMEOUT
)
assert resp.status_code == 200
assert resp.json()["answer"] == expected
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ select = ["E", "F", "I", "W", "B", "UP"]
# black owns line length; don't double-enforce it (it won't wrap comments/strings)
ignore = ["E501"]

[tool.pytest.ini_options]
# `rbs unit-test` runs a bare `pytest`, which would otherwise collect from the repo root and
# sweep up integration/ — a suite that needs a running container. Keep the default run to tests/;
# `rbs integration-test` names integration/ explicitly, which overrides this.
testpaths = ["tests"]

[tool.coverage.run]
source = ["quantumsolver"]

Expand Down
5 changes: 4 additions & 1 deletion rbs.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ port = 27100
health-path = "/health"

[integration]
command = "uv run pytest tests/integration -v"
# Lives outside tests/ so a bare `pytest` (what `rbs unit-test` runs) never collects it;
# see [tool.pytest.ini_options] in pyproject.toml. No [[integration.services]] needed —
# quantumsolver is a leaf, so the ephemeral stack is just the artifact itself.
command = "uv run pytest integration -v"

[push]
on-branch = "main"
Loading