168 lines
7.1 KiB
Python
168 lines
7.1 KiB
Python
from __future__ import annotations
|
|
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
from test_deploy_broker import deploy_module as _deploy_fixture
|
|
from test_hub_component_transport import controller as _controller_fixture
|
|
|
|
|
|
@pytest.fixture
|
|
def deploy_module(monkeypatch, tmp_path):
|
|
return _deploy_fixture.__wrapped__(monkeypatch, tmp_path)
|
|
|
|
|
|
@pytest.fixture
|
|
def controller(monkeypatch):
|
|
return _controller_fixture.__wrapped__(monkeypatch)
|
|
|
|
|
|
@pytest.mark.parametrize("mode", ["deploy", "rollback"])
|
|
def test_original_git_objects_are_bound_before_identity_reads(deploy_module, monkeypatch, mode):
|
|
monkeypatch.delenv("APPOPS_REGISTERED_LEGACY_REQUEST_ID", raising=False)
|
|
monkeypatch.delenv("GIT_NO_REPLACE_OBJECTS", raising=False)
|
|
assert deploy_module.bind_registered_legacy_request({}, mode) == ""
|
|
assert deploy_module.os.environ["GIT_NO_REPLACE_OBJECTS"] == "1"
|
|
|
|
|
|
def test_known_registered_restore_keeps_verified_revision_and_fresh_runtime_ids(deploy_module):
|
|
prior_revision = "a" * 40
|
|
restored_artifacts = {
|
|
"containers": {
|
|
"quiz-monitor-quiz-server-1": {
|
|
"container_id": "b" * 64,
|
|
"image_id": "sha256:" + "c" * 64,
|
|
}
|
|
},
|
|
"registered_legacy": {"requestId": "restored-request", "revision": prior_revision},
|
|
}
|
|
restored_receipt = {"revision": prior_revision, "containers": restored_artifacts["containers"]}
|
|
project = {
|
|
"_registered_legacy_restored": True,
|
|
"_registered_legacy_restored_revision": prior_revision,
|
|
"_registered_legacy_restored_artifacts": restored_artifacts,
|
|
"_registered_legacy_restored_receipt": restored_receipt,
|
|
}
|
|
result = deploy_module.resolve_failed_live_state(
|
|
{"live_revision": prior_revision, "live_status": "healthy"},
|
|
{"revision": prior_revision, "legacy_provenance": False, "registered_legacy": {}},
|
|
project,
|
|
production_started=True,
|
|
failure_stage="verification",
|
|
restore_error=None,
|
|
)
|
|
|
|
assert result["live_revision"] == prior_revision
|
|
assert result["live_status"] == "healthy"
|
|
assert result["live_legacy_provenance"] is False
|
|
assert result["live_artifacts"] == restored_artifacts
|
|
assert result["live_receipt"] == restored_receipt
|
|
|
|
|
|
def test_failed_known_registered_restore_keeps_provenance_unknown(deploy_module):
|
|
prior_revision = "a" * 40
|
|
result = deploy_module.resolve_failed_live_state(
|
|
{"live_revision": prior_revision, "live_status": "healthy"},
|
|
{"revision": prior_revision, "legacy_provenance": False, "registered_legacy": {}},
|
|
{},
|
|
production_started=True,
|
|
failure_stage="verification",
|
|
restore_error=RuntimeError("restore failed"),
|
|
)
|
|
|
|
assert result["live_status"] == "unknown"
|
|
assert result["live_artifacts"] == {}
|
|
assert result["live_receipt"] == {}
|
|
|
|
|
|
def test_known_restore_source_callback_precedes_verification_and_state_binding(
|
|
deploy_module, monkeypatch
|
|
):
|
|
previous_revision = "a" * 40
|
|
failed_revision = "d" * 40
|
|
image_ids = {"quiz-monitor-quiz-server-1": "sha256:" + "c" * 64}
|
|
restored_receipt = {"revision": previous_revision, "build": {"revision": previous_revision}}
|
|
artifacts = {"containers": {}, "registered_legacy": {"revision": previous_revision}}
|
|
events = []
|
|
project = {}
|
|
|
|
def core_restore(_repository, _project, _snapshot, _failed, **kwargs):
|
|
events.append("signed-preimage-verified")
|
|
restore_source = kwargs.get("restore_source")
|
|
assert callable(restore_source), "known restore requires a source callback after signed validation"
|
|
restore_source(previous_revision)
|
|
events.append("restored-runtime-signed")
|
|
return {"revision": previous_revision, "artifacts": artifacts,
|
|
"imageIDs": image_ids, "liveReceipt": restored_receipt}
|
|
|
|
def source_restore(_project, _repository, revision):
|
|
assert revision == previous_revision
|
|
events.append("prior-source-restored")
|
|
|
|
def verify(_project, metadata, *, expected_image_ids):
|
|
assert metadata["revision"] == previous_revision
|
|
assert expected_image_ids == image_ids
|
|
events.append("health-and-provenance-verified")
|
|
return restored_receipt
|
|
|
|
monkeypatch.setattr(deploy_module, "read_state", lambda _project: {})
|
|
monkeypatch.setattr(deploy_module, "_registered_legacy_receipt_key", lambda: b"k" * 32)
|
|
monkeypatch.setattr(deploy_module, "_load_registered_legacy_adoption", lambda: SimpleNamespace(
|
|
restore_deployment_snapshot=core_restore
|
|
))
|
|
monkeypatch.setattr(deploy_module, "restore_live_source", source_restore)
|
|
monkeypatch.setattr(deploy_module, "validate_recorded_receipt", lambda *_args: restored_receipt["build"])
|
|
monkeypatch.setattr(deploy_module, "verify_deployment", verify)
|
|
|
|
deploy_module.restore_compose_snapshot(
|
|
project,
|
|
{"revision": previous_revision, "legacy_provenance": False, "registered_legacy": {}},
|
|
"jens/quiz-monitor", failed_revision,
|
|
)
|
|
|
|
assert events == ["signed-preimage-verified", "prior-source-restored",
|
|
"restored-runtime-signed", "health-and-provenance-verified"]
|
|
assert project["_registered_legacy_restored"] is True
|
|
assert project["_registered_legacy_restored_revision"] == previous_revision
|
|
assert project["_registered_legacy_restored_receipt"] == restored_receipt
|
|
|
|
|
|
@pytest.mark.parametrize("receipt_matches", [True, False])
|
|
def test_typed_rollback_worker_supplies_fixed_source_and_provenance_verifiers(
|
|
controller, monkeypatch, receipt_matches
|
|
):
|
|
import sys
|
|
|
|
module, projects = controller
|
|
repository = "jens/quiz-monitor"
|
|
previous_revision = "a" * 40
|
|
receipt = {"revision": previous_revision, "build": {"revision": previous_revision}}
|
|
project = {"slug": "quiz-monitor", "source_dir": "/fixed/source"}
|
|
projects[repository] = project
|
|
calls = []
|
|
|
|
def rollback(_payload, **kwargs):
|
|
kwargs["restore_source"](previous_revision)
|
|
kwargs["verify_restored"]({"revision": previous_revision, "liveReceipt": receipt,
|
|
"imageIDs": {"quiz": "immutable-image"}})
|
|
calls.append("state-cas")
|
|
|
|
monkeypatch.setitem(sys.modules, "registered_legacy_adoption", SimpleNamespace(
|
|
rollback=rollback, read_registered_state=lambda *_a: ({}, None),
|
|
RegisteredLegacyAdoptionError=ValueError,
|
|
record_worker_failure=lambda *_a, **_k: calls.append("failed"),
|
|
))
|
|
monkeypatch.setitem(sys.modules, "deploy", SimpleNamespace(
|
|
restore_live_source=lambda selected, repo, revision: calls.append(
|
|
("source", selected, repo, revision)),
|
|
validate_recorded_receipt=lambda selected, raw, repo, revision: receipt["build"],
|
|
verify_deployment=lambda selected, build, **kwargs: calls.append("verified") or (
|
|
receipt if receipt_matches else {**receipt, "revision": "f" * 40}
|
|
),
|
|
))
|
|
module._run_registered_legacy_rollback({
|
|
"repository": repository, "requestId": "11111111-1111-4111-8111-111111111111",
|
|
}, b"k" * 32)
|
|
assert calls == [("source", project, repository, previous_revision), "verified",
|
|
"state-cas" if receipt_matches else "failed"]
|