""" Тесты: save_run — ручной UPSERT (UPDATE -> INSERT). """ import json import db.save_run as sv class FakeCursor: def __init__(self): self.executed = [] self.rowcount = 0 # UPDATE not affected (no existing row) def execute(self, sql, params=None): self.executed.append((sql, params)) def close(self): pass class FakeConn: def __init__(self): self.committed = False self.rolled_back = False self.cursor_obj = FakeCursor() def cursor(self): return self.cursor_obj def commit(self): self.committed = True def rollback(self): self.rolled_back = True def test_first_call_inserts(monkeypatch): """Первый вызов save_run (op_uid=None) -> INSERT.""" conn = FakeConn() monkeypatch.setattr(sv, "get_conn", lambda: conn) put_calls = [] monkeypatch.setattr(sv, "put_conn", lambda c: put_calls.append(c)) sv.save_run("cid", "test", "u@e", 1, "", "create", 18, None, "inst-1", "display", "RUNNING", 0, "", {}, [], "1.0") sqls = [e[0].strip()[:20] for e in conn.cursor_obj.executed] assert any("INSERT INTO runs" in s for s in sqls), f"Expected INSERT, got {sqls}" assert conn.committed assert len(put_calls) == 1 def test_second_call_updates(monkeypatch): """Второй вызов с тем же op_uid -> UPDATE, не INSERT.""" conn = FakeConn() monkeypatch.setattr(sv, "get_conn", lambda: conn) monkeypatch.setattr(sv, "put_conn", lambda c: None) # Первый вызов — INSERT (rowcount=0) sv.save_run("cid", "test", "u@e", 1, "svc", "create", 18, "op-123", "inst-1", "display", "RUNNING", 0, "", {"k": "v"}, [], "1.0") # Второй вызов — UPDATE (теперь строка существует) conn.cursor_obj.rowcount = 1 sv.save_run("cid", "test", "u@e", 1, "svc-final", "create", 18, "op-123", "inst-1", "display", "OK", 1.5, "", {"k": "v"}, [{"stage": 1}], "1.0", instance_meta={"cpu": 2}) sqls = [e[0] for e in conn.cursor_obj.executed] updates = [s for s in sqls if "UPDATE runs SET" in s] inserts = [s for s in sqls if "INSERT INTO runs" in s] # Первый вызов: UPDATE (rowcount=0) + INSERT. Второй: UPDATE (rowcount=1). assert len(updates) == 2, f"Expected 2 UPDATEs, got {len(updates)}" assert len(inserts) == 1, f"Expected 1 INSERT, got {len(inserts)}" assert conn.committed def test_update_with_new_status(monkeypatch): """UPDATE меняет status с RUNNING на OK.""" conn = FakeConn() monkeypatch.setattr(sv, "get_conn", lambda: conn) monkeypatch.setattr(sv, "put_conn", lambda c: None) # Первый вызов — INSERT (rowcount=0 по умолчанию) sv.save_run("cid", "test", "u@e", 1, "", "create", 18, "op-456", "inst-2", "d", "RUNNING", 0, "", {}, [], "1.0") # Второй вызов — UPDATE (rowcount=1) conn.cursor_obj.rowcount = 1 sv.save_run("cid", "test", "u@e", 1, "final-svc", "create", 18, "op-456", "inst-2", "d", "OK", 2.0, "err", {}, [{"s": 1}], "1.0", instance_meta={"ram": 4}) # Найти ПОСЛЕДНИЙ UPDATE-запрос и проверить параметры update_params = None for sql, params in conn.cursor_obj.executed: if "UPDATE runs SET" in sql: update_params = params assert update_params is not None, "UPDATE not found" assert update_params[0] == "OK" # status assert update_params[1] == 2.0 # duration assert update_params[2] == "err" # error_log assert update_params[4] == "final-svc" # svc_name assert update_params[6] == "op-456" # op_uid def test_db_unavailable_skips(monkeypatch): """Без БД save_run не падает.""" monkeypatch.setattr(sv, "get_conn", lambda: None) # Не должно быть исключений sv.save_run("cid", "test", "", 1, "", "create", 18, "op-x", "inst-x", "d", "OK", 0, "", {}, [], "1.0")