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();