From 0209ec0d5ca42bc76c32ef8dbd6c23b0c2559145 Mon Sep 17 00:00:00 2001 From: Colin Maudry Date: Fri, 3 Jul 2026 09:34:09 +0200 Subject: [PATCH] feat(admin): add subscription history, status override, and statuses constant Adds four new DB helpers for the admin panel: - SUBSCRIPTION_STATUSES: tuple of valid subscription statuses - list_by_user(): retrieve all subscriptions for a user (newest first) - set_status(): override a subscription's status and updated_at timestamp - get_subscriber_state(): public wrapper for internal _get_state() Co-Authored-By: Claude Sonnet 5 --- src/subscriptions/db.py | 25 ++++++++++++++++++ tests/subscriptions/test_db.py | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/src/subscriptions/db.py b/src/subscriptions/db.py index 39516b4..9b9457a 100644 --- a/src/subscriptions/db.py +++ b/src/subscriptions/db.py @@ -123,6 +123,27 @@ def get_current(user_id: int) -> sqlite3.Row | None: ) +SUBSCRIPTION_STATUSES = ("active", "trial", "cancelled", "expired", "pending") + + +def list_by_user(user_id: int) -> list[sqlite3.Row]: + return ( + get_conn() + .execute( + "SELECT * FROM subscriptions WHERE user_id = ? ORDER BY id DESC", + (user_id,), + ) + .fetchall() + ) + + +def set_status(subscription_id: int, status: str) -> None: + get_conn().execute( + "UPDATE subscriptions SET status = ?, updated_at = ? WHERE id = ?", + (status, _now(), subscription_id), + ) + + def get_by_handle(subscription_handle: str) -> sqlite3.Row | None: return ( get_conn() @@ -210,6 +231,10 @@ def _get_state(user_id: int) -> sqlite3.Row | None: ) +def get_subscriber_state(user_id: int) -> sqlite3.Row | None: + return _get_state(user_id) + + def freeze_votes_cursor(user_id: int) -> None: """Réactivation après une période sans abonnement : repart de maintenant. diff --git a/tests/subscriptions/test_db.py b/tests/subscriptions/test_db.py index ffa8330..aec92fe 100644 --- a/tests/subscriptions/test_db.py +++ b/tests/subscriptions/test_db.py @@ -326,3 +326,51 @@ def test_trial_to_active_does_not_reset_then_grants_two(users_db_path): db.update_from_webhook(handle, "active", _future()) # fin d'essai : credit_pending accorde les +INITIAL_VOTES initiaux assert db.credit_pending(uid) == db.INITIAL_VOTES + + +def test_list_by_user_returns_most_recent_first(users_db_path): + uid = _make_user() + db.init_schema() + _handle1, sub_id1 = db.create_pending(uid, "cust-1", "simple") + _handle2, sub_id2 = db.create_pending(uid, "cust-1", "soutien") + + rows = db.list_by_user(uid) + + assert [r["id"] for r in rows] == [sub_id2, sub_id1] + + +def test_set_status_updates_status(users_db_path): + uid = _make_user() + db.init_schema() + _handle, sub_id = db.create_pending(uid, "cust-1", "simple") + + db.set_status(sub_id, "active") + + row = db.get_current(uid) + assert row["status"] == "active" + + +def test_get_subscriber_state_returns_row_after_create_pending(users_db_path): + uid = _make_user() + db.init_schema() + db.create_pending(uid, "cust-1", "simple") + + state = db.get_subscriber_state(uid) + + assert state is not None + assert state["user_id"] == uid + + +def test_get_subscriber_state_returns_none_for_unknown_user(users_db_path): + db.init_schema() + assert db.get_subscriber_state(999999) is None + + +def test_subscription_statuses_constant(): + assert db.SUBSCRIPTION_STATUSES == ( + "active", + "trial", + "cancelled", + "expired", + "pending", + )