Match tests to the reworked dashboard, surface the renewals KPI

The club table was rebuilt (logo, status badges, Plan/Dues columns, an Edit
action) and the dashboard dropped the second chart, so three render tests were
asserting columns and a canvas that no longer exist. Updated to the current
layout — the service-level tests were already correct, since the annotations they
check still exist even where the template stopped rendering them.

Worked the renewals KPI into the billing card: "N awaiting renewal", shown only
when non-zero. It should sit at 0 in normal running — the cron renews clubs 30
days out and they fall past the horizon — so a number here means the job has
stopped and a club is about to use the platform free, which nothing else on the
page reveals because nothing has been billed yet.

Fixed two things in the WIP table while here: a debug line that printed the raw
grace/period/owed values into the Dues cell, and a missing {% empty %} clause
(so an empty list showed a headed table with no "no clubs" row, and empty_message
was dead). Removed the stale commented-out copy of the old table.

Answers "auto-renewed but unpaid?": it is not a special case. Renewal opens an
ordinary unpaid Due, which flows unpaid -> grace -> overdue -> archive like any
period — so the safety net that the never-billed club slipped past now fires,
because there is a due to be overdue. Tested both ways.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 08:34:49 +02:00
parent 9a616c20e4
commit 975426a17f
5 changed files with 184 additions and 154 deletions

View File

@@ -751,8 +751,8 @@ class DashboardMetricsTests(ControlPanelTestBase):
self.assertContains(response, "No current season")
self.assertContains(response, "MFA pending")
self.assertContains(response, "Payment pending")
self.assertContains(response, 'id="signups-chart"')
self.assertContains(response, 'id="revenue-chart"')
self.assertContains(response, "js/chart.js")
self.assertIn("signups", response.context["charts"])
@@ -1014,18 +1014,17 @@ class ClubHealthTableTests(TestCase):
response = self.client.get(reverse("controlpanel:dashboard"))
self.assertContains(response, "Owed")
self.assertContains(response, "Upcoming")
self.assertContains(response, "Unpaid")
# Health, not vanity: Plan and Dues each name something to act on, next to the counts.
for column in ("Members", "Admins", "Teams", "Events", "Plan", "Dues"):
self.assertContains(response, f">{column}</th>")
class ClubListHealthTests(ControlPanelTestBase):
def test_the_list_shows_the_same_health_columns_as_the_dashboard(self):
response = self.client.get(reverse("controlpanel:club_list"))
self.assertContains(response, "Owed")
self.assertContains(response, "Upcoming")
self.assertContains(response, "Unpaid")
for column in ("Members", "Admins", "Teams", "Events", "Plan", "Dues"):
self.assertContains(response, f">{column}</th>")
self.assertTemplateUsed(response, "controlpanel/_club_health_table.html")
def test_an_archived_club_is_badged_archived_rather_than_dormant(self):
@@ -1097,6 +1096,24 @@ class PlatformDuesMetricTests(TestCase):
self.assertEqual(platform_attention()["clubs_unbilled"], 0)
def test_renewals_pending_counts_clubs_about_to_lapse(self):
# ~0 in normal running; a number here means the renewal cron has stopped.
self.assertEqual(platform_attention()["renewals_pending"], 0)
subscribe(self.club, self.tier, start=self.today - datetime.timedelta(days=350)) # ends in 15 days
self.assertEqual(platform_attention()["renewals_pending"], 1)
def test_the_dashboard_surfaces_pending_renewals(self):
# The whole point of the KPI: a club about to go free is visible, though nothing is
# owed yet, so no other figure on the page would show it.
subscribe(self.club, self.tier, start=self.today - datetime.timedelta(days=350))
staff = User.objects.create_user(email="staff@example.com", password="pw-secret-123", is_staff=True)
enrol_mfa(staff)
self.client.force_login(staff)
self.assertContains(self.client.get(reverse("controlpanel:dashboard")), "awaiting renewal")
def test_platform_dues_and_club_shop_money_are_different_charts(self):
subscribe(self.club, self.tier)
record_payment(self.club.dues.first(), Decimal("500.00"))