generated from coulomb/repo-seed
Implement HTTP ingestion and retention lifecycle
This commit is contained in:
@@ -2,8 +2,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, insert, inspect
|
||||
@@ -84,3 +86,169 @@ def test_cli_health_reports_ok(
|
||||
assert payload["status"] == "ok"
|
||||
assert payload["db"]["healthy"] is True
|
||||
assert payload["backend"]["healthy"] is True
|
||||
|
||||
|
||||
def test_cli_push_uses_http_api(
|
||||
runner: CliRunner,
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
source = tmp_path / "source"
|
||||
source.mkdir()
|
||||
(source / "a.txt").write_text("alpha", encoding="utf-8")
|
||||
calls: list[tuple[str, str, dict[str, Any]]] = []
|
||||
multipart_calls: list[tuple[str, dict[str, str], bytes]] = []
|
||||
|
||||
def fake_http_json(
|
||||
method: str,
|
||||
base_url: str,
|
||||
path: str,
|
||||
token: str,
|
||||
payload: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
calls.append((method, path, payload))
|
||||
assert base_url == "http://api.test"
|
||||
assert token == "secret"
|
||||
if path == "/packages":
|
||||
return {"id": "pkg-1"}
|
||||
if path == "/packages/pkg-1/finalize":
|
||||
return {"manifest_digest": "blake3:abc"}
|
||||
raise AssertionError(f"unexpected JSON request: {method} {path}")
|
||||
|
||||
def fake_http_multipart(
|
||||
base_url: str,
|
||||
path: str,
|
||||
token: str,
|
||||
*,
|
||||
fields: dict[str, str],
|
||||
file_field: str,
|
||||
file_name: str,
|
||||
file_content_type: str,
|
||||
file_bytes: bytes,
|
||||
) -> dict[str, Any]:
|
||||
assert base_url == "http://api.test"
|
||||
assert token == "secret"
|
||||
assert path == "/packages/pkg-1/files"
|
||||
assert file_field == "file"
|
||||
assert file_name == "a.txt"
|
||||
assert file_content_type == "text/plain"
|
||||
multipart_calls.append((path, fields, file_bytes))
|
||||
return {"id": "file-1"}
|
||||
|
||||
monkeypatch.setattr("artifactstore.cli._http_json", fake_http_json)
|
||||
monkeypatch.setattr("artifactstore.cli._http_multipart", fake_http_multipart)
|
||||
|
||||
result = runner.invoke(
|
||||
cli_app,
|
||||
[
|
||||
"push",
|
||||
str(source),
|
||||
"--producer",
|
||||
"prod",
|
||||
"--subject",
|
||||
"sub",
|
||||
"--api-url",
|
||||
"http://api.test",
|
||||
"--token",
|
||||
"secret",
|
||||
],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert json.loads(result.output) == {
|
||||
"package_id": "pkg-1",
|
||||
"manifest_digest": "blake3:abc",
|
||||
"files": 1,
|
||||
}
|
||||
assert calls[0][1] == "/packages"
|
||||
assert calls[1][1] == "/packages/pkg-1/finalize"
|
||||
assert multipart_calls == [
|
||||
(
|
||||
"/packages/pkg-1/files",
|
||||
{"relative_path": "a.txt", "media_type": "text/plain"},
|
||||
b"alpha",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def test_cli_manifest_fetches_json_projection(
|
||||
runner: CliRunner,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
def fake_http_bytes(
|
||||
method: str,
|
||||
base_url: str,
|
||||
path: str,
|
||||
token: str,
|
||||
*,
|
||||
body: bytes | None = None,
|
||||
headers: dict[str, str] | None = None,
|
||||
) -> bytes:
|
||||
assert method == "GET"
|
||||
assert base_url == "http://api.test"
|
||||
assert path == "/packages/pkg-1/manifest.json"
|
||||
assert token == "secret"
|
||||
assert body is None
|
||||
assert headers == {"Accept": "application/json"}
|
||||
return b'{"manifest_version":1}'
|
||||
|
||||
monkeypatch.setattr("artifactstore.cli._http_bytes", fake_http_bytes)
|
||||
|
||||
result = runner.invoke(
|
||||
cli_app,
|
||||
[
|
||||
"manifest",
|
||||
"pkg-1",
|
||||
"--api-url",
|
||||
"http://api.test",
|
||||
"--token",
|
||||
"secret",
|
||||
],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert json.loads(result.output) == {"manifest_version": 1}
|
||||
|
||||
|
||||
def test_cli_retention_sweep_marks_expired_package(
|
||||
runner: CliRunner,
|
||||
env_db: Path,
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
sync_engine = create_engine(f"sqlite:///{env_db}", future=True)
|
||||
metadata.create_all(sync_engine)
|
||||
with sync_engine.begin() as conn:
|
||||
conn.execute(insert(retention_classes), [dict(s) for s in RETENTION_CLASS_SEEDS])
|
||||
sync_engine.dispose()
|
||||
|
||||
retention_config = tmp_path / "retention.toml"
|
||||
retention_config.write_text(
|
||||
'[retention_classes.transient]\ndefault_duration_seconds = 0\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setenv("ARTIFACTSTORE_RETENTION_CONFIG_PATH", str(retention_config))
|
||||
|
||||
async def create_expired_package() -> str:
|
||||
from artifactstore.app import build_registry
|
||||
from artifactstore.config import get_settings
|
||||
|
||||
registry = build_registry(get_settings())
|
||||
try:
|
||||
package_id = await registry.create_package(
|
||||
name="expired",
|
||||
producer="tests",
|
||||
subject="cli-sweep",
|
||||
retention_class="transient",
|
||||
actor="ops",
|
||||
)
|
||||
finally:
|
||||
await registry.dispose()
|
||||
return str(package_id)
|
||||
|
||||
package_id = asyncio.run(create_expired_package())
|
||||
result = runner.invoke(cli_app, ["retention", "sweep"])
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
payload = json.loads(result.output)
|
||||
assert payload == {"marked_package_ids": [package_id], "marked_count": 1}
|
||||
|
||||
Reference in New Issue
Block a user