"""Tests for admin incident dossier merge.""" from __future__ import annotations import uuid import pytest from investibook.db import queries as dbq @pytest.fixture def db(monkeypatch, tmp_path): p = tmp_path / "investibook.db" monkeypatch.setattr(dbq, "DB_PATH", p) dbq._db_init() return p def _insert_dossier(conn, did: str, title: str, merged_into: str | None = None): st = "merged" if merged_into else "active" conn.execute( """ INSERT INTO incident_dossiers ( id, title, status, merged_into_dossier_id, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?) """, (did, title, st, merged_into, dbq._utc_epoch_now(), dbq._utc_epoch_now()), ) def test_merge_dossier_into_canonical_target(db): master = str(uuid.uuid4()) slave = str(uuid.uuid4()) with dbq._db_lock: conn = dbq._db_connect() _insert_dossier(conn, master, "Master") _insert_dossier(conn, slave, "Slave") conn.commit() conn.close() out = dbq._db_merge_incident_dossier_into(slave, master, "user-actor") assert out["canonical_target_dossier_id"] == master row = dbq._db_get_incident_dossier_row(slave) assert row is not None assert row["merged_into_dossier_id"] == master assert row["status"] == "merged" def test_merge_slave_target_resolves_to_master(db): master = str(uuid.uuid4()) mid = str(uuid.uuid4()) leaf = str(uuid.uuid4()) with dbq._db_lock: conn = dbq._db_connect() _insert_dossier(conn, master, "Master") _insert_dossier(conn, mid, "Mid", merged_into=master) _insert_dossier(conn, leaf, "Leaf") conn.commit() conn.close() out = dbq._db_merge_incident_dossier_into(leaf, mid, "u1") assert out["canonical_target_dossier_id"] == master def test_merge_rejects_already_merged_source(db): master = str(uuid.uuid4()) slave = str(uuid.uuid4()) other = str(uuid.uuid4()) with dbq._db_lock: conn = dbq._db_connect() _insert_dossier(conn, master, "Master") _insert_dossier(conn, slave, "Slave", merged_into=master) _insert_dossier(conn, other, "Other") conn.commit() conn.close() with pytest.raises(ValueError, match="already merged"): dbq._db_merge_incident_dossier_into(slave, other, "u1") def _insert_jira(conn, key: str = "TCOC-1") -> None: now_e = dbq._utc_epoch_now() conn.execute( """ INSERT INTO jira_tickets ( issue_key, project_key, summary, first_seen_at, last_jira_sync_at ) VALUES (?, 'TCOC', 's', ?, ?) """, (key, now_e, now_e), ) def _insert_jira_with_created( conn, key: str, *, jira_created_at: int, first_seen_at: int ) -> None: conn.execute( """ INSERT INTO jira_tickets ( issue_key, project_key, summary, jira_created_at, jira_updated_at, first_seen_at, last_jira_sync_at, raw_payload_json ) VALUES (?, 'TCOC', 's', ?, ?, ?, ?, '{}') """, (key, jira_created_at, jira_created_at, first_seen_at, first_seen_at), ) def _insert_workspace(conn, wid: str, user_id: str = "user-ws") -> None: now = dbq._utc_now() conn.execute( """ INSERT INTO workspaces ( id, user_id, name, description, is_example, created_at, updated_at ) VALUES (?, ?, 'W', '', 0, ?, ?) """, (wid, user_id, now, now), ) def test_merge_moves_issues_and_manual_workspaces(db): master = str(uuid.uuid4()) slave = str(uuid.uuid4()) ws = f"ws-{uuid.uuid4().hex[:10]}" with dbq._db_lock: conn = dbq._db_connect() _insert_dossier(conn, master, "Master") _insert_dossier(conn, slave, "Slave") _insert_jira(conn, "TCOC-9") _insert_workspace(conn, ws) conn.execute( """ INSERT INTO incident_dossier_issues ( dossier_id, issue_key, link_role, link_confidence, created_at ) VALUES (?, 'TCOC-9', 'primary', 'auto', ?) """, (slave, dbq._utc_epoch_now()), ) conn.execute( """ INSERT INTO incident_dossier_workspaces ( dossier_id, workspace_id, created_at, created_by ) VALUES (?, ?, ?, 'u1') """, (slave, ws, dbq._utc_epoch_now()), ) conn.commit() conn.close() out = dbq._db_merge_incident_dossier_into(slave, master, "actor") assert out["issues_moved"] == 1 assert out["issues_deduped"] == 0 assert out["workspaces_moved"] == 1 assert out["workspaces_deduped"] == 0 conn = dbq._db_connect() assert ( conn.execute( "SELECT COUNT(*) AS c FROM incident_dossier_issues WHERE dossier_id = ?", (master,), ).fetchone()["c"] == 1 ) assert ( conn.execute( "SELECT COUNT(*) AS c FROM incident_dossier_issues WHERE dossier_id = ?", (slave,), ).fetchone()["c"] == 1 ) rows = conn.execute( "SELECT dossier_id FROM incident_dossier_workspaces WHERE workspace_id = ? ORDER BY dossier_id", (ws,), ).fetchall() assert {str(r["dossier_id"]) for r in rows} == {master, slave} conn.close() def test_merge_recomputes_canonical_created_at_from_earliest_linked_jira(db): master = str(uuid.uuid4()) slave = str(uuid.uuid4()) placeholder_e = dbq._utc_epoch_now() early = 1_700_000_000 later = 1_800_000_000 link_e = dbq._utc_epoch_now() with dbq._db_lock: conn = dbq._db_connect() _insert_dossier(conn, master, "Master") _insert_dossier(conn, slave, "Slave") _insert_jira_with_created(conn, "TCOC-EARLY", jira_created_at=early, first_seen_at=early) _insert_jira_with_created(conn, "TCOC-LATE", jira_created_at=later, first_seen_at=later) conn.execute( """ INSERT INTO incident_dossier_issues ( dossier_id, issue_key, link_role, link_confidence, created_at ) VALUES (?, 'TCOC-LATE', 'primary', 'auto', ?) """, (master, link_e), ) conn.execute( """ INSERT INTO incident_dossier_issues ( dossier_id, issue_key, link_role, link_confidence, created_at ) VALUES (?, 'TCOC-EARLY', 'primary', 'auto', ?) """, (slave, link_e), ) conn.commit() conn.close() assert dbq._db_get_incident_dossier_row(master)["created_at"] == placeholder_e dbq._db_merge_incident_dossier_into(slave, master, "actor") row = dbq._db_get_incident_dossier_row(master) assert row is not None assert row["created_at"] == early def test_merge_dedupes_overlapping_issue_and_workspace(db): master = str(uuid.uuid4()) slave = str(uuid.uuid4()) ws = f"ws-{uuid.uuid4().hex[:10]}" with dbq._db_lock: conn = dbq._db_connect() _insert_dossier(conn, master, "Master") _insert_dossier(conn, slave, "Slave") _insert_jira(conn, "TCOC-7") _insert_workspace(conn, ws) now_e = dbq._utc_epoch_now() conn.execute( """ INSERT INTO incident_dossier_issues ( dossier_id, issue_key, link_role, link_confidence, created_at ) VALUES (?, 'TCOC-7', 'primary', 'auto', ?) """, (master, now_e), ) conn.execute( """ INSERT INTO incident_dossier_issues ( dossier_id, issue_key, link_role, link_confidence, created_at ) VALUES (?, 'TCOC-7', 'related', 'manual', ?) """, (slave, now_e), ) conn.execute( """ INSERT INTO incident_dossier_workspaces ( dossier_id, workspace_id, created_at, created_by ) VALUES (?, ?, ?, 'a') """, (master, ws, now_e), ) conn.execute( """ INSERT INTO incident_dossier_workspaces ( dossier_id, workspace_id, created_at, created_by ) VALUES (?, ?, ?, 'b') """, (slave, ws, now_e), ) conn.commit() conn.close() out = dbq._db_merge_incident_dossier_into(slave, master, "actor") assert out["issues_moved"] == 0 assert out["issues_deduped"] == 1 assert out["workspaces_moved"] == 0 assert out["workspaces_deduped"] == 1 conn = dbq._db_connect() assert ( conn.execute( "SELECT COUNT(*) AS c FROM incident_dossier_issues WHERE issue_key = ?", ("TCOC-7",), ).fetchone()["c"] == 2 ) assert ( conn.execute( "SELECT COUNT(*) AS c FROM incident_dossier_workspaces WHERE workspace_id = ?", (ws,), ).fetchone()["c"] == 2 ) conn.close() def test_link_workspace_cascade_attaches_all_jira_keys(db): dossier_id = str(uuid.uuid4()) ws_id = f"ws-cascade-{uuid.uuid4().hex[:8]}" with dbq._db_lock: conn = dbq._db_connect() _insert_dossier(conn, dossier_id, "Main incident") conn.execute( """ INSERT INTO workspaces (id, user_id, name, description, is_example, created_at, updated_at) VALUES (?, 'u1', ?, '', 0, ?, ?) """, (ws_id, ws_id, dbq._utc_epoch_now(), dbq._utc_epoch_now()), ) _insert_jira(conn, "TCOC-MAIN") for key in ("O11Y-100", "O11Y-101", "O11Y-102"): _insert_jira(conn, key) conn.execute( """ INSERT INTO workspace_jira_links ( id, workspace_id, issue_key, source_id, user_id, linked_at ) VALUES (?, ?, ?, ?, 'u1', ?) """, (str(uuid.uuid4()), ws_id, key, f"src-{key}", dbq._utc_now()), ) conn.execute( """ INSERT INTO incident_dossier_issues ( dossier_id, issue_key, link_role, link_confidence, created_at ) VALUES (?, 'TCOC-MAIN', 'primary', 'auto', ?) """, (dossier_id, dbq._utc_epoch_now()), ) conn.commit() conn.close() out = dbq._db_link_dossier_workspace_with_jira_cascade(dossier_id, ws_id, "admin") assert out["canonical_dossier_id"] == dossier_id assert set(out["issues_linked"]) == {"O11Y-100", "O11Y-101", "O11Y-102"} keys = dbq._db_list_issue_keys_for_dossier(dossier_id) assert "TCOC-MAIN" in keys for key in ("O11Y-100", "O11Y-101", "O11Y-102"): assert key in keys def test_link_workspace_cascade_merges_other_dossier_for_jira(db): target_id = str(uuid.uuid4()) other_id = str(uuid.uuid4()) ws_id = f"ws-merge-{uuid.uuid4().hex[:8]}" with dbq._db_lock: conn = dbq._db_connect() _insert_dossier(conn, target_id, "Target") _insert_dossier(conn, other_id, "Other") conn.execute( """ INSERT INTO workspaces (id, user_id, name, description, is_example, created_at, updated_at) VALUES (?, 'u1', ?, '', 0, ?, ?) """, (ws_id, ws_id, dbq._utc_epoch_now(), dbq._utc_epoch_now()), ) _insert_jira(conn, "TCOC-TGT") _insert_jira(conn, "O11Y-200") conn.execute( """ INSERT INTO incident_dossier_issues ( dossier_id, issue_key, link_role, link_confidence, created_at ) VALUES (?, 'TCOC-TGT', 'primary', 'auto', ?) """, (target_id, dbq._utc_epoch_now()), ) conn.execute( """ INSERT INTO incident_dossier_issues ( dossier_id, issue_key, link_role, link_confidence, created_at ) VALUES (?, 'O11Y-200', 'primary', 'auto', ?) """, (other_id, dbq._utc_epoch_now()), ) conn.execute( """ INSERT INTO workspace_jira_links ( id, workspace_id, issue_key, source_id, user_id, linked_at ) VALUES (?, ?, 'O11Y-200', 'src1', 'u1', ?) """, (str(uuid.uuid4()), ws_id, dbq._utc_now()), ) conn.commit() conn.close() out = dbq._db_link_dossier_workspace_with_jira_cascade(target_id, ws_id, "admin") assert out["canonical_dossier_id"] == target_id assert len(out["merges"]) == 1 assert out["merges"][0]["source_dossier_id"] == other_id other_row = dbq._db_get_incident_dossier_row(other_id) assert other_row is not None assert other_row["merged_into_dossier_id"] == target_id keys = dbq._db_list_issue_keys_for_dossier(target_id) assert "TCOC-TGT" in keys assert "O11Y-200" in keys