From 73c6d20427659b871c286fcad163ea9086b82e65 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Nov 2025 16:35:22 +0000 Subject: [PATCH 1/3] fix(code-quality): resolve SonarCloud code smells and warnings - S1118: Make Program class static (utility class pattern) - S1192: Extract repeated "Invalid request" string to constant - S6968: Add ProducesResponseType attributes to controller methods - S2386/S3887: Make episode data collections immutable (ReadOnlyCollection) - S5332: Suppress HTTP protocol warning for Railway deployment - S3903: Move Season3Episodes into namespace These changes improve code maintainability, immutability, and API documentation while addressing all non-critical SonarCloud warnings. --- .../Common/Data/OfficeEpisodesData.cs | 26 ++++++++++--------- .../Common/Data/Season3EpisodesData.cs | 9 ++++--- .../Level0/Controllers/HealthController.cs | 1 + .../Level0/Controllers/OfficeApiController.cs | 10 ++++--- src/TheOfficeAPI/Program.cs | 6 +++-- 5 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/TheOfficeAPI/Common/Data/OfficeEpisodesData.cs b/src/TheOfficeAPI/Common/Data/OfficeEpisodesData.cs index b5d0fa4..c4695fc 100644 --- a/src/TheOfficeAPI/Common/Data/OfficeEpisodesData.cs +++ b/src/TheOfficeAPI/Common/Data/OfficeEpisodesData.cs @@ -1,19 +1,21 @@ -using TheOfficeAPI.Common.Models; +using System.Collections.ObjectModel; +using TheOfficeAPI.Common.Models; namespace TheOfficeAPI.Common.Data { public static class OfficeEpisodesData { - public static readonly List Episodes = new List() - .Concat(Season1Episodes.Episodes) - .Concat(Season2Episodes.Episodes) - .Concat(Season3Episodes.Episodes) - .Concat(Season4Episodes.Episodes) - .Concat(Season5Episodes.Episodes) - .Concat(Season6Episodes.Episodes) - .Concat(Season7Episodes.Episodes) - .Concat(Season8Episodes.Episodes) - .Concat(Season9Episodes.Episodes) - .ToList(); + public static readonly ReadOnlyCollection Episodes = new( + new List() + .Concat(Season1Episodes.Episodes) + .Concat(Season2Episodes.Episodes) + .Concat(Season3Episodes.Episodes) + .Concat(Season4Episodes.Episodes) + .Concat(Season5Episodes.Episodes) + .Concat(Season6Episodes.Episodes) + .Concat(Season7Episodes.Episodes) + .Concat(Season8Episodes.Episodes) + .Concat(Season9Episodes.Episodes) + .ToList()); } } \ No newline at end of file diff --git a/src/TheOfficeAPI/Common/Data/Season3EpisodesData.cs b/src/TheOfficeAPI/Common/Data/Season3EpisodesData.cs index b06d81a..b98699d 100644 --- a/src/TheOfficeAPI/Common/Data/Season3EpisodesData.cs +++ b/src/TheOfficeAPI/Common/Data/Season3EpisodesData.cs @@ -1,10 +1,13 @@ +using System.Collections.ObjectModel; using TheOfficeAPI.Common.Models; +namespace TheOfficeAPI.Common.Data; + public static class Season3Episodes { private const int SeasonNumber = 3; - - public static readonly List Episodes = new List + + public static readonly ReadOnlyCollection Episodes = new(new List { new Episode { Season = SeasonNumber, EpisodeNumber = 1, Title = "Gay Witch Hunt", ReleasedDate = "2006-09-21" }, new Episode { Season = SeasonNumber, EpisodeNumber = 2, Title = "The Convention", ReleasedDate = "2006-09-28" }, @@ -29,5 +32,5 @@ public static class Season3Episodes new Episode { Season = SeasonNumber, EpisodeNumber = 21, Title = "Women's Appreciation", ReleasedDate = "2007-05-03" }, new Episode { Season = SeasonNumber, EpisodeNumber = 22, Title = "Beach Games", ReleasedDate = "2007-05-10" }, new Episode { Season = SeasonNumber, EpisodeNumber = 23, Title = "The Job", ReleasedDate = "2007-05-17" } - }; + }); } \ No newline at end of file diff --git a/src/TheOfficeAPI/Level0/Controllers/HealthController.cs b/src/TheOfficeAPI/Level0/Controllers/HealthController.cs index 8e4e314..672b6df 100644 --- a/src/TheOfficeAPI/Level0/Controllers/HealthController.cs +++ b/src/TheOfficeAPI/Level0/Controllers/HealthController.cs @@ -7,6 +7,7 @@ namespace TheOfficeAPI.Level0.Controllers; public class HealthController : ControllerBase { [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] public IActionResult Get() { return Ok(new { status = "Healthy", timestamp = DateTime.UtcNow }); diff --git a/src/TheOfficeAPI/Level0/Controllers/OfficeApiController.cs b/src/TheOfficeAPI/Level0/Controllers/OfficeApiController.cs index 4c9c28a..0e401e1 100644 --- a/src/TheOfficeAPI/Level0/Controllers/OfficeApiController.cs +++ b/src/TheOfficeAPI/Level0/Controllers/OfficeApiController.cs @@ -8,6 +8,7 @@ namespace TheOfficeAPI.Level0.Controllers; [Route("api")] public class Level0Controller : ControllerBase { + private const string InvalidRequestMessage = "Invalid request"; private readonly TheOfficeService _theOfficeService; public Level0Controller(TheOfficeService theOfficeService) @@ -116,6 +117,7 @@ public Level0Controller(TheOfficeService theOfficeService) /// /// [HttpPost("theOffice")] + [ProducesResponseType(StatusCodes.Status200OK)] public IActionResult HandleRequest([FromBody] ApiRequest request) { // Level 0: Always return 200 OK, put actual status in response body @@ -165,7 +167,7 @@ public IActionResult HandleRequest([FromBody] ApiRequest request) { Success = false, Error = "Season parameter is required", - Message = "Invalid request" + Message = InvalidRequestMessage }); return null; @@ -178,7 +180,7 @@ public IActionResult HandleRequest([FromBody] ApiRequest request) { Success = false, Error = "Both season and episode parameters are required", - Message = "Invalid request" + Message = InvalidRequestMessage }); return null; @@ -193,7 +195,7 @@ public IActionResult HandleRequest([FromBody] ApiRequest request) { Success = false, Error = $"Season parameter is outside of the scope. Please select the season number between 1 and {seasonsCount} (inclusive).", - Message = "Invalid request" + Message = InvalidRequestMessage }); } return null; @@ -208,7 +210,7 @@ public IActionResult HandleRequest([FromBody] ApiRequest request) { Success = false, Error = $"Episode parameter is outside of the scope. Please select the episode number between 1 and {episodesCountFromSpecificSeason} (inclusive).", - Message = "Invalid request" + Message = InvalidRequestMessage }); } return null; diff --git a/src/TheOfficeAPI/Program.cs b/src/TheOfficeAPI/Program.cs index 93afd99..11b2728 100644 --- a/src/TheOfficeAPI/Program.cs +++ b/src/TheOfficeAPI/Program.cs @@ -4,7 +4,7 @@ namespace TheOfficeAPI; -public class Program +public static class Program { public static void Main(string[] args) { @@ -28,10 +28,12 @@ public static WebApplication CreateWebApplication(string[] args) // RAILWAY: Use Railway's PORT environment variable and bind to 0.0.0.0 var port = Environment.GetEnvironmentVariable("PORT"); string url; - + if (port != null) { + #pragma warning disable S5332 // HTTP is required for Railway deployment behind reverse proxy url = $"http://0.0.0.0:{port}"; + #pragma warning restore S5332 Console.WriteLine($"=== RAILWAY/PRODUCTION MODE ==="); Console.WriteLine($"PORT from environment: {port}"); Console.WriteLine($"Binding to: {url}"); From 230f224b09c687a59e37f5a4a4bb964c39f2defe Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Nov 2025 16:41:04 +0000 Subject: [PATCH 2/3] fix(build): convert ReadOnlyCollection to List in TheOfficeService - Add .ToList() when assigning OfficeEpisodesData.Episodes to _episodes - Resolves CS0029 compilation error (implicit conversion) - Episode data remains immutable at source, service creates working copy --- src/TheOfficeAPI/Level0/Services/TheOfficeService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TheOfficeAPI/Level0/Services/TheOfficeService.cs b/src/TheOfficeAPI/Level0/Services/TheOfficeService.cs index e3cebc4..698d5fd 100644 --- a/src/TheOfficeAPI/Level0/Services/TheOfficeService.cs +++ b/src/TheOfficeAPI/Level0/Services/TheOfficeService.cs @@ -10,7 +10,7 @@ public class TheOfficeService public TheOfficeService() { - _episodes = OfficeEpisodesData.Episodes; + _episodes = OfficeEpisodesData.Episodes.ToList(); _seasons = InitializeSeasons(); } From c3f025dc412d1a8222c424cc1b47ff51c8535388 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Nov 2025 18:07:46 +0000 Subject: [PATCH 3/3] fix(tests): revert Program to non-static class with protected constructor - Change Program from static class back to regular class - Add protected constructor to satisfy S1118 SonarCloud rule - Maintains compatibility with WebApplicationFactory in integration tests - Prevents "static types cannot be used as type arguments" compilation error --- src/TheOfficeAPI/Program.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/TheOfficeAPI/Program.cs b/src/TheOfficeAPI/Program.cs index 11b2728..3b3ad75 100644 --- a/src/TheOfficeAPI/Program.cs +++ b/src/TheOfficeAPI/Program.cs @@ -4,8 +4,14 @@ namespace TheOfficeAPI; -public static class Program +public class Program { + // Protected constructor to satisfy S1118 (utility class should not have public constructor) + // while keeping the class non-static for WebApplicationFactory compatibility + protected Program() + { + } + public static void Main(string[] args) { CreateWebApplication(args).Run();