"""disconnect_all_sources revokes every active source a tenant has connected --
the counterpart to wiping a tenant's whole catalogue, so a still-connected
source can't silently repopulate what was just deleted on the next build.

Fully isolated from a real database -- get_master_db_connection is mocked in
every test.
"""
from unittest.mock import MagicMock

from app.services.catalog import sources as sources_module
from app.services.catalog.sources import disconnect_all_sources


def _mock_conn(monkeypatch, rowcount):
    monkeypatch.setattr(sources_module, "ensure_table", lambda: None)
    conn = MagicMock()
    cur = conn.cursor.return_value.__enter__.return_value
    cur.rowcount = rowcount
    monkeypatch.setattr(
        "app.services.catalog.sources.get_master_db_connection", lambda: conn)
    return conn, cur


def test_revokes_every_active_source_for_the_tenant(monkeypatch):
    conn, cur = _mock_conn(monkeypatch, rowcount=2)

    count = disconnect_all_sources("org_x")

    assert count == 2
    cur.execute.assert_called_once()
    sql, params = cur.execute.call_args[0]
    assert "revoked" in sql
    assert "disconnected_at" in sql
    assert params == ("org_x",)
    conn.commit.assert_called_once()


def test_a_tenant_with_no_active_sources_disconnects_none(monkeypatch):
    conn, cur = _mock_conn(monkeypatch, rowcount=0)
    assert disconnect_all_sources("org_x") == 0
    conn.commit.assert_called_once()


def test_a_db_error_rolls_back_and_raises(monkeypatch):
    monkeypatch.setattr(sources_module, "ensure_table", lambda: None)
    conn = MagicMock()
    conn.cursor.side_effect = Exception("connection refused")
    monkeypatch.setattr(
        "app.services.catalog.sources.get_master_db_connection", lambda: conn)

    try:
        disconnect_all_sources("org_x")
        assert False, "expected an exception"
    except Exception:
        pass

    conn.rollback.assert_called_once()
