CROSSWIRE / tests /test_server.py
Kai9987kai's picture
Publish checked budgeted experiments from GitHub 3354331226d1
d0e78ed verified
Raw History Blame Contribute Delete
4.3 kB
import json
import threading
import time
from urllib.error import HTTPError
from urllib.request import Request, urlopen
import pytest
from crosswire.server import make_server
@pytest.fixture
def endpoint():
server = make_server(port=0)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
yield f'http://127.0.0.1:{server.server_port}'
server.shutdown()
server.server_close()
server.manager.close()
thread.join(3)
def send(base, path, data=None, headers=None):
request = Request(base + path, data=json.dumps(data).encode() if data is not None else None,
headers={'Content-Type': 'application/json', **(headers or {})})
with urlopen(request, timeout=30) as response:
return json.load(response)
def completed(base, job):
until = time.monotonic() + 25
while time.monotonic() < until:
value = send(base, '/api/runs/' + job['id'])
if value['status'] in ('COMPLETE', 'FAILED', 'CANCELLED'):
return value
time.sleep(.05)
raise AssertionError('Job did not finish')
def test_http_run_export_import_and_replay_use_real_worker(endpoint):
cat = send(endpoint, '/api/catalog')
assert 'task' in cat['policies']
job = send(endpoint, '/api/runs', {'family': 'symmetric', 'budget': 0})
value = completed(endpoint, job)
assert value['status'] == 'COMPLETE'
assert value['result']['decision']['admitted'] is False
receipt = send(endpoint, f"/api/runs/{job['id']}/export")
imported = send(endpoint, '/api/import', {'receipt': receipt})
assert imported['status'] == 'QUEUED'
assert completed(endpoint, imported)['result']['scientific_hash'] == value['result']['scientific_hash']
replay_job = send(endpoint, f"/api/runs/{job['id']}/replay", {})
assert replay_job['status'] == 'QUEUED'
assert completed(endpoint, replay_job)['result']['valid'] is True
def test_api_rejects_invalid_configuration_and_cross_origin_mutation(endpoint):
with pytest.raises(HTTPError) as exc:
send(endpoint, '/api/runs', {'budget': True})
assert exc.value.code == 400
with pytest.raises(HTTPError) as exc:
send(endpoint, '/api/runs', {}, {'Origin': 'https://unrelated.example'})
assert exc.value.code == 403
def test_cancelled_study_cannot_be_exported_as_complete(endpoint):
job = send(endpoint, '/api/studies', {'families': ['latch', 'handshake'], 'seeds': [17, 23, 29],
'budgets': [8, 16], 'policies': ['task', 'fixed']})
send(endpoint, f"/api/runs/{job['id']}/cancel", {})
assert completed(endpoint, job)['status'] == 'CANCELLED'
with pytest.raises(HTTPError) as exc:
send(endpoint, f"/api/runs/{job['id']}/export")
assert exc.value.code == 409
def test_static_server_cannot_read_source_or_parent_paths(endpoint):
with urlopen(endpoint) as response:
assert b'CROSSWIRE' in response.read()
for path in ('/../pyproject.toml', '/crosswire/fixtures.py'):
with pytest.raises(HTTPError) as exc:
urlopen(endpoint + path)
assert exc.value.code == 404
def test_replay_status_is_not_exportable_scientific_evidence(endpoint):
job = send(endpoint, '/api/runs', {'family': 'symmetric', 'budget': 0})
assert completed(endpoint, job)['status'] == 'COMPLETE'
replay_job = send(endpoint, f"/api/runs/{job['id']}/replay", {})
assert completed(endpoint, replay_job)['result']['valid'] is True
with pytest.raises(HTTPError) as exc:
send(endpoint, f"/api/runs/{replay_job['id']}/export")
assert exc.value.code == 409
assert 'evidence' in json.load(exc.value)['error'].lower()
def test_export_source_change_returns_json_conflict(endpoint, monkeypatch):
job = send(endpoint, '/api/runs', {'family': 'symmetric', 'budget': 0})
assert completed(endpoint, job)['status'] == 'COMPLETE'
monkeypatch.setattr('crosswire.receipts.source_hash', lambda: 'changed-source')
with pytest.raises(HTTPError) as exc:
send(endpoint, f"/api/runs/{job['id']}/export")
assert exc.value.code == 409
assert 'source' in json.load(exc.value)['error'].lower()