Reconcile lost upload responses without replaying frame writes
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import json
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
import pytest
|
||||
from test_display import submit
|
||||
|
||||
from pigway_frame_flow.errors import ErrorCode, FlowError
|
||||
|
||||
|
||||
def lose_response(service, monkeypatch, change_display=True):
|
||||
driver = service.registry.get("living-room")
|
||||
upload = driver.upload
|
||||
|
||||
def uncertain(path, name):
|
||||
upload(path, name)
|
||||
if not change_display:
|
||||
driver.client.display(1)
|
||||
raise FlowError(
|
||||
ErrorCode.WRITE_RESULT_UNKNOWN, "Cloud write outcome is unknown", 502
|
||||
)
|
||||
|
||||
monkeypatch.setattr(driver, "upload", uncertain)
|
||||
operation = submit(service)
|
||||
service.process_one()
|
||||
assert service.operation(operation)["status"] == "UNKNOWN"
|
||||
return operation, driver
|
||||
|
||||
|
||||
def test_lost_upload_response_resolves_by_exact_bytes_without_reupload(
|
||||
service, monkeypatch
|
||||
):
|
||||
operation, driver = lose_response(service, monkeypatch)
|
||||
service.sync()
|
||||
assert service.operation(operation)["status"] == "COMPLETED"
|
||||
assert service.operation(operation)["result"]["image_id"] == 2
|
||||
assert driver.client.uploads == 1
|
||||
assert not service.blocked("living-room")
|
||||
|
||||
|
||||
def test_same_name_wrong_bytes_does_not_unlock(service, monkeypatch):
|
||||
operation, driver = lose_response(service, monkeypatch)
|
||||
monkeypatch.setattr(driver, "download", lambda _: b"unrelated")
|
||||
service.sync()
|
||||
assert service.operation(operation)["status"] == "UNKNOWN"
|
||||
assert service.blocked("living-room")
|
||||
assert driver.client.uploads == 1
|
||||
|
||||
|
||||
def test_ambiguous_gallery_does_not_download_or_unlock(service, monkeypatch):
|
||||
operation, driver = lose_response(service, monkeypatch)
|
||||
driver.client.state["images"].append(dict(driver.client.state["images"][-1], id=3))
|
||||
monkeypatch.setattr(driver, "download", lambda _: pytest.fail("Ambiguous download"))
|
||||
service.sync()
|
||||
assert service.operation(operation)["status"] == "UNKNOWN"
|
||||
|
||||
|
||||
def test_confirmed_upload_not_displayed_closes_failed_display_after_stable_checks(
|
||||
service, monkeypatch
|
||||
):
|
||||
operation, driver = lose_response(service, monkeypatch, change_display=False)
|
||||
service.sync()
|
||||
result = service.operation(operation)["result"]
|
||||
assert result["upload_confirmed"]
|
||||
start = datetime.fromisoformat(result["upload_intent"]["started_at"])
|
||||
import pigway_frame_flow.display.upload_reconciliation as module
|
||||
|
||||
class Later(datetime):
|
||||
@classmethod
|
||||
def now(cls, tz=None):
|
||||
return start + timedelta(seconds=service.settings.sync_timeout + 600)
|
||||
|
||||
monkeypatch.setattr(module, "datetime", Later)
|
||||
service.unknown_checked.clear()
|
||||
service.sync()
|
||||
assert service.operation(operation)["status"] == "UNKNOWN"
|
||||
result = service.operation(operation)["result"]
|
||||
result["unchanged_observed_at"] = (
|
||||
datetime.now(UTC) - timedelta(seconds=61)
|
||||
).isoformat()
|
||||
service.db.execute(
|
||||
"UPDATE operations SET result=? WHERE id=?", (json.dumps(result), operation)
|
||||
)
|
||||
service.unknown_checked.clear()
|
||||
service.sync()
|
||||
assert service.operation(operation)["status"] == "FAILED"
|
||||
assert service.operation(operation)["error"]["code"] == "DISPLAY_NOT_CONFIRMED"
|
||||
assert not service.blocked("living-room")
|
||||
assert driver.client.uploads == 1
|
||||
|
||||
|
||||
def test_legacy_unknown_without_intent_remains_locked(service):
|
||||
operation = submit(service)
|
||||
service._start_write(operation)
|
||||
service.update(operation, "UNKNOWN")
|
||||
service.sync()
|
||||
assert service.blocked("living-room")
|
||||
|
||||
|
||||
def test_recovered_upload_preserves_temporary_restore(service, monkeypatch):
|
||||
driver = service.registry.get("living-room")
|
||||
upload = driver.upload
|
||||
|
||||
def uncertain(path, name):
|
||||
upload(path, name)
|
||||
raise FlowError(ErrorCode.WRITE_RESULT_UNKNOWN, "Response lost", 502)
|
||||
|
||||
monkeypatch.setattr(driver, "upload", uncertain)
|
||||
operation = submit(service, duration=60, restore=True)
|
||||
service.process_one()
|
||||
service.sync()
|
||||
assert service.operation(operation)["status"] == "COMPLETED"
|
||||
assert service.top("living-room")["id"] == operation
|
||||
assert service.state("living-room")["managed_image_id"] == 2
|
||||
Reference in New Issue
Block a user