Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@
# checksums and making every refresh from upstream show a whole-file diff.
src/oold/validation/meta/*/** -text
tests/data/oold/** -text

# tests/data/resolver_cache/ holds a Resolver disk-cache entry, named by the sha256 of a fixed
# URL. It carries no upstream checksum, but the same autocrlf rewrite would still turn it into
# a CRLF file, which is not what the "written with LF" test fixture convention expects.
tests/data/resolver_cache/** -text
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"$id": "https://example.org/oold-warm-cache-fixture.schema.json",
"title": "Warm",
"type": "object"
}
11 changes: 11 additions & 0 deletions tests/test_validation/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
COMPLIANCE = DATA / "compliance"
REMOTE_CONTEXT = DATA / "remote_context"
X_OOLD_CONTEXT = DATA / "x_oold_context"
RESOLVER_CACHE = DATA.parent / "resolver_cache"


@pytest.fixture
Expand Down Expand Up @@ -43,6 +44,16 @@ def x_oold_context_dir() -> Path:
return X_OOLD_CONTEXT


@pytest.fixture
def resolver_cache_dir() -> Path:
"""A committed `Resolver` disk-cache entry, named by the sha256 digest of a fixed URL.

Proves a warmed cache satisfies an offline fetch without a network mock standing in for the
retrieval layer itself.
"""
return RESOLVER_CACHE


@pytest.fixture
def isolated_cache(tmp_path, monkeypatch) -> Path:
"""Point the document and meta caches at a temporary directory.
Expand Down
84 changes: 84 additions & 0 deletions tests/test_validation/test_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

from __future__ import annotations

import functools
import http.server
import json
import threading

import pytest

from oold.validation import Options, run_compliance, validate_directory, validate_instance, validate_schema
Expand Down Expand Up @@ -67,6 +72,85 @@ def test_a_context_chain_leaving_the_directory_resolves(remote_context_dir):
assert "context.remote" in _ids(report, OK)


def test_a_genuinely_remote_context_is_fetched_over_http(tmp_path):
"""`remote_context_dir` above only ever leaves the *directory*; nothing in the corpus makes
an `@context` chain leave the filesystem, so `Resolver` never actually opens a socket. That
matters because socket retrieval is exactly the layer #119 proposes moving onto
`referencing`, and no fixture touching it means a regression there would pass CI.

A loopback HTTP server, bound to an OS-assigned port and shut down in a `finally`, is the
genuinely remote sibling: the schema and its remote context are templated into `tmp_path`
(a committed fixture cannot carry a port that varies per run), and the handler records every
request it serves, so the assertion below is that the context was actually fetched over the
wire, not resolved from disk by accident.
"""
served = tmp_path / "served"
served.mkdir()
requested: list[str] = []

class RecordingHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self) -> None:
requested.append(self.path)
super().do_GET()

def log_message(self, format: str, *args: object) -> None:
pass # keep the test's own output free of per-request access logging

handler = functools.partial(RecordingHandler, directory=str(served))
server = http.server.ThreadingHTTPServer(("127.0.0.1", 0), handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()

try:
base_url = f"http://127.0.0.1:{server.server_port}/"
(served / "Thing.schema.json").write_text(
json.dumps({
"$schema": "https://oo-ld.org/latest/meta/oold-meta-schema.json",
"$id": "Thing.schema.json",
"x-oold-instance-rdf-type": ["schema:Thing"],
"@context": {"schema": "http://schema.org/", "name": "schema:name"},
"title": "Thing",
"type": "object",
"properties": {"name": {"type": "string"}},
}),
encoding="utf-8",
)

case_dir = tmp_path / "case"
case_dir.mkdir()
(case_dir / "RemoteLeaf.schema.json").write_text(
json.dumps({
"$schema": "https://oo-ld.org/latest/meta/oold-meta-schema.json",
"$id": "RemoteLeaf.schema.json",
"title": "RemoteLeaf",
"x-oold-instance-rdf-type": ["schema:Thing"],
"@context": [
f"{base_url}Thing.schema.json",
{"schema": "http://schema.org/", "nickname": "schema:alternateName"},
],
"type": "object",
"required": ["name"],
"properties": {"name": {"type": "string"}, "nickname": {"type": "string"}},
}),
encoding="utf-8",
)

report = validate_schema(
case_dir / "RemoteLeaf.schema.json",
Options(meta=("latest",), offline=False, cache_dir=tmp_path / "cache"),
)
finally:
server.shutdown()
server.server_close()
thread.join()

assert report.passed, [f"{c.id}: {c.message}" for c in report.failures()]
assert "context.remote" in _ids(report, OK)
assert any(path.endswith("Thing.schema.json") for path in requested), (
f"the context was never actually requested over the socket: {requested!r}"
)


def test_a_property_mapped_only_through_x_oold_context_is_not_reported(x_oold_context_dir):
"""x-oold-context (OOLD-EXT-4966) is a legitimate way to map a term; one mapped only there
must not be reported as unmapped just because plain JSON-LD cannot see that mapping."""
Expand Down
16 changes: 16 additions & 0 deletions tests/test_validation/test_resolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,22 @@ def test_properties_nesting_is_depth_neutral():
assert bound_schema(nested, max_depth=2) == nested


def test_a_warm_cache_entry_resolves_offline_and_a_missing_one_is_refused(resolver_cache_dir):
"""`tests/data/resolver_cache/` commits one entry ahead of time, named by the sha256 digest
`Resolver._cache_file` itself derives from the URL. That is what lets this prove the offline
guarantee the vendored meta-schemas depend on - a previously warmed cache satisfies a fetch -
without a network mock standing in for the retrieval layer #119 is about to move onto
`referencing`.
"""
resolver = Resolver(cache_dir=resolver_cache_dir, offline=True)

document = resolver.fetch("https://example.org/oold-warm-cache-fixture.schema.json")
assert document["title"] == "Warm"

with pytest.raises(SchemaResolutionError, match="offline"):
resolver.fetch("https://example.org/oold-warm-cache-fixture-missing.schema.json")


def test_disk_cache_is_reused(tmp_path, monkeypatch):
cache = tmp_path / "cache"
calls = []
Expand Down
Loading