Use live maintenance sessions, durable capacity reservations and streamed SHA-bound ZIP uploads. An ordinary capped import worker validates stored/deflated v1/v2 packages, persists immutable unpublished candidates and finite audit events, and recovers or releases capacity after interruption and expiry. Validate actual PG/HTTP/worker/storage fault paths in an isolated capped VM (2 passed, 0 skipped), reuse unchanged actual migration roundtrip evidence, and pass affected backend/architecture checks plus the prior full engineering checks.
157 lines
5.4 KiB
Python
157 lines
5.4 KiB
Python
"""Contract checks only; upload/parser/security evidence requires the independent VM."""
|
|
|
|
import copy
|
|
import json
|
|
from datetime import UTC, datetime, timedelta
|
|
from pathlib import Path
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from kf_oj.contracts.admin_problems import (
|
|
CandidatePreview,
|
|
CreateProblemImport,
|
|
LegacyImportBudget,
|
|
ProblemImportCapabilities,
|
|
ProblemImportDetail,
|
|
)
|
|
from kf_oj.contracts.judging import AdmissionPolicy
|
|
from kf_oj.modules.problems.import_format import ImportEnvelope
|
|
from kf_oj.modules.problems.web_format import decode_web_declaration
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def legacy_manifest():
|
|
policy = AdmissionPolicy.model_validate_json(
|
|
(ROOT / "tests/fixtures/contracts/judge-admission.valid.json").read_bytes()
|
|
)
|
|
return {
|
|
"schema_version": 1,
|
|
"slug": "web-fixture",
|
|
"title": "Public",
|
|
"statement_file": "statement.md",
|
|
"languages": ["c17", "cpp20"],
|
|
"toolchain": policy.toolchain.model_dump(mode="json"),
|
|
"comparison": "exact_bytes",
|
|
"compile_limits": policy.compile_ceiling.model_dump(),
|
|
"run_limits": policy.run_ceiling.model_dump(),
|
|
"job_wall_time_us": 60000000,
|
|
"cases": [{"key": "binary", "input_file": "hidden.in", "expected_file": "hidden.out"}],
|
|
"samples": [{"input_file": "sample.in", "output_file": "sample.out"}],
|
|
}
|
|
|
|
|
|
def test_web_declaration_separates_public_data_and_preserves_local_format():
|
|
source = legacy_manifest()
|
|
original = copy.deepcopy(source)
|
|
declared = decode_web_declaration(json.dumps(source).encode())
|
|
assert declared.members == frozenset(
|
|
("problem.json", "statement.md", "hidden.in", "hidden.out", "sample.in", "sample.out")
|
|
)
|
|
assert declared.manifest.model_dump(mode="json") == original
|
|
assert ImportEnvelope.model_validate(source).root == declared.manifest
|
|
for mutation in (
|
|
lambda x: x["samples"][0].update(input_file="hidden.in"),
|
|
lambda x: x.update(statement_file="problem.json"),
|
|
lambda x: x["cases"][0].update(input_file="Statement.md"),
|
|
lambda x: x["cases"][0].update(input_file="hidden.zip"),
|
|
lambda x: x.update(languages=["rust"]),
|
|
lambda x: x.update(download_url="https://example.invalid/private"),
|
|
):
|
|
value = copy.deepcopy(source)
|
|
mutation(value)
|
|
with pytest.raises(ValueError):
|
|
decode_web_declaration(json.dumps(value).encode())
|
|
with pytest.raises(ValueError):
|
|
decode_web_declaration(
|
|
json.dumps(source)
|
|
.replace('"schema_version": 1', '"schema_version": 1, "schema_version": 1')
|
|
.encode()
|
|
)
|
|
|
|
|
|
def test_import_operation_key_and_preview_have_no_private_payload_fields():
|
|
operation = {
|
|
"schema_version": 1,
|
|
"operation_id": str(uuid4()),
|
|
"archive_digest": "a" * 64,
|
|
"archive_bytes": 64 * 1024**2,
|
|
}
|
|
assert CreateProblemImport.model_validate(operation).archive_bytes == 64 * 1024**2
|
|
for field, value in (
|
|
("archive_bytes", True),
|
|
("archive_bytes", 64 * 1024**2 + 1),
|
|
("url", "https://example.invalid"),
|
|
("role", "admin"),
|
|
):
|
|
with pytest.raises(ValidationError):
|
|
CreateProblemImport.model_validate(operation | {field: value})
|
|
manifest = ImportEnvelope.model_validate(legacy_manifest()).root
|
|
preview = CandidatePreview(
|
|
candidate_id=uuid4(),
|
|
slug=manifest.slug,
|
|
title=manifest.title,
|
|
statement="Public only",
|
|
samples=(),
|
|
judging_schema_version=1,
|
|
languages=manifest.languages,
|
|
legacy_budget=LegacyImportBudget(
|
|
compile_limits=manifest.compile_limits,
|
|
run_limits=manifest.run_limits,
|
|
job_wall_time_us=manifest.job_wall_time_us,
|
|
),
|
|
case_count=1,
|
|
)
|
|
now = datetime.now(UTC)
|
|
response = ProblemImportDetail(
|
|
operation_id=uuid4(),
|
|
state="ready",
|
|
expected_archive_digest="a" * 64,
|
|
archive_bytes=100,
|
|
created_at=now,
|
|
updated_at=now,
|
|
expires_at=now + timedelta(days=1),
|
|
failure_code=None,
|
|
candidate_id=preview.candidate_id,
|
|
published_version_id=None,
|
|
candidate=preview,
|
|
)
|
|
assert set(response.candidate.model_dump()) == {
|
|
"candidate_id",
|
|
"slug",
|
|
"title",
|
|
"statement",
|
|
"samples",
|
|
"judging_schema_version",
|
|
"languages",
|
|
"legacy_budget",
|
|
"language_configurations",
|
|
"case_count",
|
|
}
|
|
with pytest.raises(ValidationError):
|
|
CandidatePreview.model_validate(preview.model_dump() | {"input_digest": "a" * 64})
|
|
capabilities = ProblemImportCapabilities(uploads_available=False, validation_available=False)
|
|
assert (
|
|
capabilities.limits.global_concurrency == 1 and capabilities.limits.owner_candidates == 10
|
|
)
|
|
|
|
|
|
def test_standalone_import_worker_resolves_all_referenced_metadata():
|
|
# A separate trusted interpreter avoids accidental model registration by API test imports.
|
|
import subprocess
|
|
import sys
|
|
|
|
script = """
|
|
import kf_oj.workers.problem_import
|
|
from kf_oj.infra.db.base import Base
|
|
for table in Base.metadata.tables.values():
|
|
for foreign in table.foreign_keys:
|
|
assert foreign.column.table is not None
|
|
"""
|
|
result = subprocess.run(
|
|
[sys.executable, "-c", script], capture_output=True, text=True, timeout=15
|
|
)
|
|
assert result.returncode == 0, result.stderr
|