+52 IT/UT to close IT/UT-only coverage gaps (93% → 96%) — full suite 983 tests in 47s ; UTs in epic/tests/unit/test_models.py — TarotCardEmanationForTest (4) covers emanation_for(polarity) w. levity/gravity overrides + fallback to name_title for cards w.o a polarity split (cards 48-49 are the only polarity-split cards in the deck so this method is sparsely exercised by ITs); TarotCardReversalForTest (4) covers reversal_for(polarity) w. polarity-split + reversal_qualifier fallback + further fallthrough to emanation_for; TarotCardNameSplitTest (4) covers name_group/name_title colon-split parsing (prefix-w-colon / suffix / no-colon edge); TarotCardCautionsJsonTest (2) covers the cautions_json JSON serialiser ; UTs in epic/tests/unit/test_utils.py — PlanetHouseFallbackTest +1 happy-path test (degree=15 lands in house 1 w. sequential cusps) for the normal cusp-match branch alongside the existing pathological fallback test; TopCapacitorsTest (6) covers all top_capacitors() branches — empty dict / None / all-zero counts (the L56 max(counts.values()) <= 0 fallback that was uncovered) / single-winner / tie-clockwise-order / enriched dict {"count":N} input shape ; ITs in epic/tests/integrated/test_models.py — TarotDeckDrawTest extended w. 5 tests for remaining_count (happy + no-deck-variant fallback to 0) + draw() happy-path (returns n tuples of (TarotCard, bool) / appends to drawn_card_ids / never repeats cards across consecutive draws); existing ValueError + shuffle tests preserved ; ITs in epic/tests/integrated/test_views.py — SigEventRetractionTest (4 tests) covers the three data["retracted"] = True paths that the FT test_game_room_select_sig.py walks transitively but no IT pins directly: sig_unready retracts prior SIG_READY (L937), sig_ready retracts prior SIG_UNREADY (L907), sig_reserve action=release while ready retracts prior SIG_READY + records fresh SIG_UNREADY (L823); SigReserveInvalidCardIdTest (1) covers TarotCard.DoesNotExist → 400 (L840-841) ; SigSelectGravityContextTest (3) covers the user_polarity = 'gravity' branch (L322) + the gravity_sig_cards lookup (L357) — all existing SIG_SELECT context tests use the founder-as-PC-levity setup so these branches sat uncovered; logs in as gamers[5] (BC role) + asserts user_polarity + sig_cards match gravity_sig_cards() output ; SeaDeckViewTest (7) mirrors the test_game_room_select_sea.py FT but isolates the JSON contract — covers 403 when unseated, empty halves when seat has no deck_variant (L1255-1256 early-out), two-halves shape, ~even split, card_dict keys (id/name/arcana/corner_rank/suit_icon/name_group/name_title/reversed/qualifiers), reversed field is bool, claimed-significator exclusion via room.table_seats.exclude(significator__isnull=True) ; ITs in dashboard/tests/integrated/test_views.py — ProfileViewTest +2 (reserved-handle "adman" rejection — L116-117: username stays unchanged + redirect to /); KitBagViewTest (3) covers the kit_bag view's panel render w. TITHE-sort branch (L169-175) + login guard ; ITs in dashboard/tests/integrated/test_sky_views.py — SkyViewTest +2 (saved birth datetime renders in user's sky_birth_tz via astimezone L300-306 — 16:00 UTC → 12:00 EDT; invalid-tz string triggers ZoneInfoNotFoundError → swallowed pass → UTC fallback at 16:00) ; ITs in gameboard/tests/integrated/test_views.py — EquipTrinketViewTest +2 (POST equips trinket + returns 204 — L83-85; non-owner POST returns 404 via get_object_or_404); UnequipTrinketViewTest +2 (POST clears matching equipped_trinket — L107-110; POST of non-matching token is a 204 no-op, the implicit else branch) ; .coveragerc omit gains */reset_staging_db.py per user — mgmt cmd was the only 0%-stmt module that wasn't exercised by tests at all + we agreed it's deliberately untested staging-side code ; palette-monochrome-dark rebalance in rootvars.scss — --quiUser/--sixUser/--sepUser remapped to (secAg / quaAg / priPt) instead of (quaAg / terAg / secAg), shifting the secondary/subtle/deep-subtle anchors up the silver gradient so the palette reads more cleanly under the new sig-stage card colours from 3242873 ; uncovered remnants from earlier analysis intentionally left in place — consumers.py at 68% (channels-tag tests excluded; would need --tag=channels run), Carte Blanche slot navigation + sky_dice + tarot_deck preview view paths (the "bigger investments" tier from session triage; FT-covered + the IT setup is heavier than the immediate value), defensive except fallbacks that need contrived inputs to fire, and a handful of __str__s/pass branches not worth a test apiece — TDD
All checks were successful
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline was successful

Code architected by Disco DeDisco <discodedisco@outlook.com>
Git commit message Co-Authored-By:
Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Disco DeDisco
2026-05-18 01:07:13 -04:00
parent 3242873625
commit bc77296dd4
9 changed files with 526 additions and 4 deletions

View File

@@ -37,6 +37,32 @@ class SkyViewTest(TestCase):
self.assertContains(response, reverse("sky_preview"))
self.assertContains(response, reverse("sky_save"))
def test_saved_birth_date_renders_in_user_tz_when_set(self):
"""A user w. saved sky_birth_dt + sky_birth_tz hits the astimezone
branch (views.py L300-306) — saved_birth_date / saved_birth_time
render in the user's local tz, not UTC."""
from datetime import datetime
import zoneinfo
# 1990-06-15 16:00 UTC = 12:00 PM in America/New_York (EDT, UTC-4)
self.user.sky_birth_dt = datetime(1990, 6, 15, 16, 0, tzinfo=zoneinfo.ZoneInfo("UTC"))
self.user.sky_birth_tz = "America/New_York"
self.user.save()
response = self.client.get(reverse("sky"))
self.assertEqual(response.context["saved_birth_date"], "1990-06-15")
self.assertEqual(response.context["saved_birth_time"], "12:00")
def test_saved_birth_falls_back_to_utc_when_tz_invalid(self):
"""A garbage sky_birth_tz triggers ZoneInfoNotFoundError — the view
swallows it (pass) and renders the UTC representation."""
from datetime import datetime
import zoneinfo
self.user.sky_birth_dt = datetime(1990, 6, 15, 16, 0, tzinfo=zoneinfo.ZoneInfo("UTC"))
self.user.sky_birth_tz = "Not/A/Real_Zone"
self.user.save()
response = self.client.get(reverse("sky"))
# UTC fallback — 16:00 stays 16:00
self.assertEqual(response.context["saved_birth_time"], "16:00")
def test_tz_input_is_readonly_and_carries_auto_detect_placeholder(self):
"""Manual TZ edits throw the schedulePreview / PySwiss fetch off (the
backend gets a stale TZ for the new lat/lon), so the field is render-

View File

@@ -461,6 +461,47 @@ class ProfileViewTest(TestCase):
[username_input] = parsed.cssselect("#id_new_username")
self.assertEqual("discoman", username_input.get("value"))
def test_post_reserved_username_does_not_save(self):
"""RESERVED_USERNAMES (e.g. 'adman') must be rejected — the view bails
with an error message + redirect to / before reaching user.save()."""
original_username = self.user.username
self.client.post("/dashboard/set_profile", data={"username": "adman"})
self.user.refresh_from_db()
self.assertEqual(self.user.username, original_username)
def test_post_reserved_username_redirects_home(self):
response = self.client.post("/dashboard/set_profile", data={"username": "adman"})
self.assertRedirects(response, "/", fetch_redirect_response=False)
class KitBagViewTest(TestCase):
"""`kit_bag` view — renders the kit-bag panel partial w. equipped + token state."""
def setUp(self):
from apps.lyric.models import Token
self.user = User.objects.create(email="gamer@test.io")
self.client.force_login(self.user)
# Stash a TITHE token so the list-comprehension branch lands non-empty
Token.objects.create(user=self.user, token_type=Token.TITHE)
self.url = "/dashboard/kit-bag/"
def test_get_returns_200_and_renders_panel(self):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "core/_partials/_kit_bag_panel.html")
def test_context_passes_free_and_tithe_counts(self):
response = self.client.get(self.url)
# signal seeds a FREE + COIN; we added a TITHE in setUp.
self.assertEqual(response.context["tithe_count"], 1)
self.assertGreaterEqual(response.context["free_count"], 0)
def test_requires_login(self):
self.client.logout()
response = self.client.get(self.url)
self.assertEqual(response.status_code, 302)
self.assertIn("/?next=", response["Location"])
class ToggleDashAppletsViewTest(TestCase):
def setUp(self):
self.user = User.objects.create(email="disco@test.io")