From 49bd631c9d6255a579c2fd54d38c5d6a579b93d2 Mon Sep 17 00:00:00 2001 From: frizat Date: Mon, 5 Oct 2026 08:56:41 -0500 Subject: [PATCH] Raise backend coverage gate to 68%; validate push subscription sport #471: backend coverage gate 30% -> 68% (baseline 72.2% lines). #462: reject a push subscription sport other than 0 (NFL) / 1 (CFB) with 400. Closes #471 Closes #462 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DAWjtJgXaMYbzZXYJjTEYs --- .github/workflows/ci.yml | 3 ++- Server.UnitTests/NotificationsControllerTests.cs | 13 +++++++++++++ Server/Controllers/NotificationsController.cs | 3 +++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63126844..98764e17 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,7 +71,8 @@ jobs: comment-title: 'Backend Unit Test Results' results-path: 'coverage/*.trx' coverage-path: 'coverage/**/coverage.cobertura.xml' - coverage-threshold: 30 + # A few points under the 2026-10-04 baseline (72.2% lines) — raise as coverage grows. + coverage-threshold: 68 coverage-type: cobertura frontend: diff --git a/Server.UnitTests/NotificationsControllerTests.cs b/Server.UnitTests/NotificationsControllerTests.cs index 03467639..a04d7f5d 100644 --- a/Server.UnitTests/NotificationsControllerTests.cs +++ b/Server.UnitTests/NotificationsControllerTests.cs @@ -112,6 +112,19 @@ public void GetVapidPublicKey_WhenOnlyPublicKeyIsSet_ReturnsNotFound() // ── Subscribe / Unsubscribe ─────────────────────────────────────────────── + // #462: only 0 (NFL) and 1 (CFB) are real sports — anything else would be stored and then never + // match a push, silently cutting that device off. + [Fact] + public async Task Subscribe_UndefinedSport_IsRejected_AndNothingIsSaved() + { + var request = new PushSubscriptionRequestDto { Endpoint = "https://push.example/ep1", P256dh = "p", Auth = "a", Sport = (LeagueType)5 }; + + var result = await BuildController("user-1").Subscribe(request); + + Assert.IsType(result); + await _subscriptionService.DidNotReceive().SubscribeAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); + } + [Fact] public async Task Subscribe_ValidRequest_CallsServiceForTheCaller() { diff --git a/Server/Controllers/NotificationsController.cs b/Server/Controllers/NotificationsController.cs index a753f657..10e819ac 100644 --- a/Server/Controllers/NotificationsController.cs +++ b/Server/Controllers/NotificationsController.cs @@ -48,6 +48,9 @@ public async Task Subscribe([FromBody] PushSubscriptionRequestDto { if (string.IsNullOrWhiteSpace(request.Endpoint) || string.IsNullOrWhiteSpace(request.P256dh) || string.IsNullOrWhiteSpace(request.Auth)) return BadRequest("Endpoint, P256dh, and Auth are required."); + // Only real sports (0 = NFL, 1 = CFB) — any other value would never match a push. + if (request.Sport is { } sport && !Enum.IsDefined(sport)) + return BadRequest("Sport must be 0 (NFL) or 1 (CFB)."); await subscriptionService.SubscribeAsync(CurrentUserId, request.Endpoint, request.P256dh, request.Auth, request.UserAgent, request.Sport); return NoContent();