From ccf9dc66f9aea64880059076ebe6cd6c20614bed Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 09:19:32 +0000 Subject: [PATCH 1/7] fix(build): revert .NET 10 upgrade and fix broken solution file The .NET 10 upgrade broke the CI pipeline because: - NuGet packages (Microsoft.AspNetCore.OpenApi 9.0.6, etc.) are incompatible with net10.0 target framework - Solution file had 5 project GUIDs replaced with duplicate zeros ({00000000-...}), causing MSBuild to fail to build those projects - Build configuration entries were removed for most test projects This reverts all projects back to net9.0, restores the solution file with unique valid GUIDs, reverts unit test SDKs from Web back to standard, and fixes the Stryker target-framework reference. https://claude.ai/code/session_01RNJdS4XPdZN1uFQiYvrCXu --- .github/workflows/cd.yaml | 2 +- .github/workflows/ci.yaml | 2 +- TheOfficeAPI.sln | 34 ++++++++++++++----- src/TheOfficeAPI/TheOfficeAPI.csproj | 2 +- stryker-config.json | 2 +- .../TheOfficeAPI.Common.Tests.Unit.csproj | 4 +-- ...eOfficeAPI.Level0.Tests.Integration.csproj | 2 +- .../TheOfficeAPI.Level0.Tests.Unit.csproj | 4 +-- ...eOfficeAPI.Level1.Tests.Integration.csproj | 2 +- .../TheOfficeAPI.Level1.Tests.Unit.csproj | 4 +-- ...eOfficeAPI.Level2.Tests.Integration.csproj | 2 +- .../TheOfficeAPI.Level2.Tests.Unit.csproj | 4 +-- ...eOfficeAPI.Level3.Tests.Integration.csproj | 2 +- .../TheOfficeAPI.Level3.Tests.Unit.csproj | 4 +-- .../TheOfficeAPI.Tests.E2E.csproj | 2 +- 15 files changed, 45 insertions(+), 27 deletions(-) diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 7583a6b..6efdbed 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -10,7 +10,7 @@ on: - develop env: - DOTNET_VERSION: '10.0.x' + DOTNET_VERSION: '9.0.x' BUILD_CONFIGURATION: 'Release' RAILWAY_URL: 'https://theofficeapi-production-5d8f.up.railway.app' diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 47ea112..03dc054 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,7 +7,7 @@ on: branches: [ main, develop ] env: - DOTNET_VERSION: '10.0.x' + DOTNET_VERSION: '9.0.x' BUILD_CONFIGURATION: 'Release' jobs: diff --git a/TheOfficeAPI.sln b/TheOfficeAPI.sln index 50ccada..3c33469 100644 --- a/TheOfficeAPI.sln +++ b/TheOfficeAPI.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheOfficeAPI", "src/TheOfficeAPI/TheOfficeAPI.csproj", "{B1D1A596-0809-4313-8A9F-AFBC87A87458}" @@ -13,15 +13,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheOfficeAPI.Level1.Tests.I EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheOfficeAPI.Level2.Tests.Unit", "tests/TheOfficeAPI.Level2.Tests.Unit/TheOfficeAPI.Level2.Tests.Unit.csproj", "{E3FA0913-3456-7890-1234-D362FE48FE0D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheOfficeAPI.Level2.Tests.Integration", "tests/TheOfficeAPI.Level2.Tests.Integration/TheOfficeAPI.Level2.Tests.Integration.csproj", "{00000000-0000-0000-0000-000000000000}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheOfficeAPI.Level2.Tests.Integration", "tests/TheOfficeAPI.Level2.Tests.Integration/TheOfficeAPI.Level2.Tests.Integration.csproj", "{9078F4DA-6AD0-4024-8764-679B805E0C28}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheOfficeAPI.Level3.Tests.Unit", "tests/TheOfficeAPI.Level3.Tests.Unit/TheOfficeAPI.Level3.Tests.Unit.csproj", "{00000000-0000-0000-0000-000000000000}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheOfficeAPI.Level3.Tests.Unit", "tests/TheOfficeAPI.Level3.Tests.Unit/TheOfficeAPI.Level3.Tests.Unit.csproj", "{DB0B4A87-CBA6-4B04-98FD-479E0E4A1E84}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheOfficeAPI.Level3.Tests.Integration", "tests/TheOfficeAPI.Level3.Tests.Integration/TheOfficeAPI.Level3.Tests.Integration.csproj", "{00000000-0000-0000-0000-000000000000}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheOfficeAPI.Level3.Tests.Integration", "tests/TheOfficeAPI.Level3.Tests.Integration/TheOfficeAPI.Level3.Tests.Integration.csproj", "{3586BC66-210E-47DD-ABCE-8DC3D0638291}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheOfficeAPI.Tests.E2E", "tests/TheOfficeAPI.Tests.E2E/TheOfficeAPI.Tests.E2E.csproj", "{00000000-0000-0000-0000-000000000000}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheOfficeAPI.Tests.E2E", "tests/TheOfficeAPI.Tests.E2E/TheOfficeAPI.Tests.E2E.csproj", "{EDE0ED8F-C286-4FFC-80BC-1972BE240130}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheOfficeAPI.Common.Tests.Unit", "tests/TheOfficeAPI.Common.Tests.Unit/TheOfficeAPI.Common.Tests.Unit.csproj", "{00000000-0000-0000-0000-000000000000}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheOfficeAPI.Common.Tests.Unit", "tests/TheOfficeAPI.Common.Tests.Unit/TheOfficeAPI.Common.Tests.Unit.csproj", "{91D7F9AC-B773-4409-90BB-C743351DB612}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -53,7 +53,25 @@ Global {E3FA0913-3456-7890-1234-D362FE48FE0D}.Debug|Any CPU.Build.0 = Debug|Any CPU {E3FA0913-3456-7890-1234-D362FE48FE0D}.Release|Any CPU.ActiveCfg = Release|Any CPU {E3FA0913-3456-7890-1234-D362FE48FE0D}.Release|Any CPU.Build.0 = Release|Any CPU - {00000000-0000-0000-0000-000000000000}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {00000000-0000-0000-0000-000000000000}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9078F4DA-6AD0-4024-8764-679B805E0C28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9078F4DA-6AD0-4024-8764-679B805E0C28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9078F4DA-6AD0-4024-8764-679B805E0C28}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9078F4DA-6AD0-4024-8764-679B805E0C28}.Release|Any CPU.Build.0 = Release|Any CPU + {DB0B4A87-CBA6-4B04-98FD-479E0E4A1E84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB0B4A87-CBA6-4B04-98FD-479E0E4A1E84}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB0B4A87-CBA6-4B04-98FD-479E0E4A1E84}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB0B4A87-CBA6-4B04-98FD-479E0E4A1E84}.Release|Any CPU.Build.0 = Release|Any CPU + {3586BC66-210E-47DD-ABCE-8DC3D0638291}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3586BC66-210E-47DD-ABCE-8DC3D0638291}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3586BC66-210E-47DD-ABCE-8DC3D0638291}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3586BC66-210E-47DD-ABCE-8DC3D0638291}.Release|Any CPU.Build.0 = Release|Any CPU + {EDE0ED8F-C286-4FFC-80BC-1972BE240130}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDE0ED8F-C286-4FFC-80BC-1972BE240130}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDE0ED8F-C286-4FFC-80BC-1972BE240130}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDE0ED8F-C286-4FFC-80BC-1972BE240130}.Release|Any CPU.Build.0 = Release|Any CPU + {91D7F9AC-B773-4409-90BB-C743351DB612}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91D7F9AC-B773-4409-90BB-C743351DB612}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91D7F9AC-B773-4409-90BB-C743351DB612}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91D7F9AC-B773-4409-90BB-C743351DB612}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/src/TheOfficeAPI/TheOfficeAPI.csproj b/src/TheOfficeAPI/TheOfficeAPI.csproj index 15824a1..1238e01 100644 --- a/src/TheOfficeAPI/TheOfficeAPI.csproj +++ b/src/TheOfficeAPI/TheOfficeAPI.csproj @@ -1,6 +1,6 @@ - net10.0 + net9.0 enable enable true diff --git a/stryker-config.json b/stryker-config.json index 75acfed..37bc3a7 100644 --- a/stryker-config.json +++ b/stryker-config.json @@ -3,7 +3,7 @@ "stryker-config": { "solution": "TheOfficeAPI.sln", "project": "src/TheOfficeAPI/TheOfficeAPI.csproj", - "target-framework": "net10.0", + "target-framework": "net9.0", "configuration": "Debug", "coverage-analysis": "perTest", "break-on-initial-test-failure": true, diff --git a/tests/TheOfficeAPI.Common.Tests.Unit/TheOfficeAPI.Common.Tests.Unit.csproj b/tests/TheOfficeAPI.Common.Tests.Unit/TheOfficeAPI.Common.Tests.Unit.csproj index bcb6e29..d7177e9 100644 --- a/tests/TheOfficeAPI.Common.Tests.Unit/TheOfficeAPI.Common.Tests.Unit.csproj +++ b/tests/TheOfficeAPI.Common.Tests.Unit/TheOfficeAPI.Common.Tests.Unit.csproj @@ -1,7 +1,7 @@ - + - net10.0 + net9.0 enable enable false diff --git a/tests/TheOfficeAPI.Level0.Tests.Integration/TheOfficeAPI.Level0.Tests.Integration.csproj b/tests/TheOfficeAPI.Level0.Tests.Integration/TheOfficeAPI.Level0.Tests.Integration.csproj index c5b505e..7eaee29 100644 --- a/tests/TheOfficeAPI.Level0.Tests.Integration/TheOfficeAPI.Level0.Tests.Integration.csproj +++ b/tests/TheOfficeAPI.Level0.Tests.Integration/TheOfficeAPI.Level0.Tests.Integration.csproj @@ -1,7 +1,7 @@ - net10.0 + net9.0 enable enable false diff --git a/tests/TheOfficeAPI.Level0.Tests.Unit/TheOfficeAPI.Level0.Tests.Unit.csproj b/tests/TheOfficeAPI.Level0.Tests.Unit/TheOfficeAPI.Level0.Tests.Unit.csproj index a0c8773..e07cf42 100644 --- a/tests/TheOfficeAPI.Level0.Tests.Unit/TheOfficeAPI.Level0.Tests.Unit.csproj +++ b/tests/TheOfficeAPI.Level0.Tests.Unit/TheOfficeAPI.Level0.Tests.Unit.csproj @@ -1,7 +1,7 @@ - + - net10.0 + net9.0 enable enable false diff --git a/tests/TheOfficeAPI.Level1.Tests.Integration/TheOfficeAPI.Level1.Tests.Integration.csproj b/tests/TheOfficeAPI.Level1.Tests.Integration/TheOfficeAPI.Level1.Tests.Integration.csproj index c5b505e..7eaee29 100644 --- a/tests/TheOfficeAPI.Level1.Tests.Integration/TheOfficeAPI.Level1.Tests.Integration.csproj +++ b/tests/TheOfficeAPI.Level1.Tests.Integration/TheOfficeAPI.Level1.Tests.Integration.csproj @@ -1,7 +1,7 @@ - net10.0 + net9.0 enable enable false diff --git a/tests/TheOfficeAPI.Level1.Tests.Unit/TheOfficeAPI.Level1.Tests.Unit.csproj b/tests/TheOfficeAPI.Level1.Tests.Unit/TheOfficeAPI.Level1.Tests.Unit.csproj index a0c8773..e07cf42 100644 --- a/tests/TheOfficeAPI.Level1.Tests.Unit/TheOfficeAPI.Level1.Tests.Unit.csproj +++ b/tests/TheOfficeAPI.Level1.Tests.Unit/TheOfficeAPI.Level1.Tests.Unit.csproj @@ -1,7 +1,7 @@ - + - net10.0 + net9.0 enable enable false diff --git a/tests/TheOfficeAPI.Level2.Tests.Integration/TheOfficeAPI.Level2.Tests.Integration.csproj b/tests/TheOfficeAPI.Level2.Tests.Integration/TheOfficeAPI.Level2.Tests.Integration.csproj index c5b505e..7eaee29 100644 --- a/tests/TheOfficeAPI.Level2.Tests.Integration/TheOfficeAPI.Level2.Tests.Integration.csproj +++ b/tests/TheOfficeAPI.Level2.Tests.Integration/TheOfficeAPI.Level2.Tests.Integration.csproj @@ -1,7 +1,7 @@ - net10.0 + net9.0 enable enable false diff --git a/tests/TheOfficeAPI.Level2.Tests.Unit/TheOfficeAPI.Level2.Tests.Unit.csproj b/tests/TheOfficeAPI.Level2.Tests.Unit/TheOfficeAPI.Level2.Tests.Unit.csproj index a0c8773..e07cf42 100644 --- a/tests/TheOfficeAPI.Level2.Tests.Unit/TheOfficeAPI.Level2.Tests.Unit.csproj +++ b/tests/TheOfficeAPI.Level2.Tests.Unit/TheOfficeAPI.Level2.Tests.Unit.csproj @@ -1,7 +1,7 @@ - + - net10.0 + net9.0 enable enable false diff --git a/tests/TheOfficeAPI.Level3.Tests.Integration/TheOfficeAPI.Level3.Tests.Integration.csproj b/tests/TheOfficeAPI.Level3.Tests.Integration/TheOfficeAPI.Level3.Tests.Integration.csproj index c5b505e..7eaee29 100644 --- a/tests/TheOfficeAPI.Level3.Tests.Integration/TheOfficeAPI.Level3.Tests.Integration.csproj +++ b/tests/TheOfficeAPI.Level3.Tests.Integration/TheOfficeAPI.Level3.Tests.Integration.csproj @@ -1,7 +1,7 @@ - net10.0 + net9.0 enable enable false diff --git a/tests/TheOfficeAPI.Level3.Tests.Unit/TheOfficeAPI.Level3.Tests.Unit.csproj b/tests/TheOfficeAPI.Level3.Tests.Unit/TheOfficeAPI.Level3.Tests.Unit.csproj index a0c8773..e07cf42 100644 --- a/tests/TheOfficeAPI.Level3.Tests.Unit/TheOfficeAPI.Level3.Tests.Unit.csproj +++ b/tests/TheOfficeAPI.Level3.Tests.Unit/TheOfficeAPI.Level3.Tests.Unit.csproj @@ -1,7 +1,7 @@ - + - net10.0 + net9.0 enable enable false diff --git a/tests/TheOfficeAPI.Tests.E2E/TheOfficeAPI.Tests.E2E.csproj b/tests/TheOfficeAPI.Tests.E2E/TheOfficeAPI.Tests.E2E.csproj index 88dd8b1..cb3bae8 100644 --- a/tests/TheOfficeAPI.Tests.E2E/TheOfficeAPI.Tests.E2E.csproj +++ b/tests/TheOfficeAPI.Tests.E2E/TheOfficeAPI.Tests.E2E.csproj @@ -1,7 +1,7 @@ - net10.0 + net9.0 enable enable false From c9d268b0ff636177c52962c1fbba9e49773928e3 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 10:17:04 +0000 Subject: [PATCH 2/7] feat(build): properly upgrade to .NET 10 with compatible package versions Update all NuGet packages to .NET 10 compatible versions: - Microsoft.AspNetCore.OpenApi: 9.0.6 -> 10.0.3 - Swashbuckle.AspNetCore: 9.0.3 -> 10.1.2 - Swashbuckle.AspNetCore.Annotations: 9.0.3 -> 10.1.2 - Microsoft.NET.Test.Sdk: 17.12.0 -> 18.0.1 - Microsoft.AspNetCore.Mvc.Testing: 9.0.11 -> 10.0.2 - Microsoft.Extensions.Hosting.Abstractions: 9.0.11 -> 10.0.3 - Microsoft.Extensions.Options: 9.0.11 -> 10.0.2 Also update Dockerfile base images to .NET 10.0 and add missing Common.Tests.Unit COPY line. https://claude.ai/code/session_01RNJdS4XPdZN1uFQiYvrCXu --- .github/workflows/cd.yaml | 2 +- .github/workflows/ci.yaml | 2 +- Directory.Packages.props | 14 +++++++------- Dockerfile | 5 +++-- src/TheOfficeAPI/TheOfficeAPI.csproj | 2 +- stryker-config.json | 2 +- .../TheOfficeAPI.Common.Tests.Unit.csproj | 2 +- .../TheOfficeAPI.Level0.Tests.Integration.csproj | 2 +- .../TheOfficeAPI.Level0.Tests.Unit.csproj | 2 +- .../TheOfficeAPI.Level1.Tests.Integration.csproj | 2 +- .../TheOfficeAPI.Level1.Tests.Unit.csproj | 2 +- .../TheOfficeAPI.Level2.Tests.Integration.csproj | 2 +- .../TheOfficeAPI.Level2.Tests.Unit.csproj | 2 +- .../TheOfficeAPI.Level3.Tests.Integration.csproj | 2 +- .../TheOfficeAPI.Level3.Tests.Unit.csproj | 2 +- .../TheOfficeAPI.Tests.E2E.csproj | 2 +- 16 files changed, 24 insertions(+), 23 deletions(-) diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 6efdbed..7583a6b 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -10,7 +10,7 @@ on: - develop env: - DOTNET_VERSION: '9.0.x' + DOTNET_VERSION: '10.0.x' BUILD_CONFIGURATION: 'Release' RAILWAY_URL: 'https://theofficeapi-production-5d8f.up.railway.app' diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 03dc054..47ea112 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,7 +7,7 @@ on: branches: [ main, develop ] env: - DOTNET_VERSION: '9.0.x' + DOTNET_VERSION: '10.0.x' BUILD_CONFIGURATION: 'Release' jobs: diff --git a/Directory.Packages.props b/Directory.Packages.props index e3f1b39..9d2f61b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,21 +5,21 @@ - - - + + + - + - - - + + + diff --git a/Dockerfile b/Dockerfile index 36858ea..2bfd702 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build stage -FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /app # Copy solution and all .csproj files @@ -14,6 +14,7 @@ COPY tests/TheOfficeAPI.Level2.Tests.Integration/*.csproj ./tests/TheOfficeAPI.L COPY tests/TheOfficeAPI.Level3.Tests.Unit/*.csproj ./tests/TheOfficeAPI.Level3.Tests.Unit/ COPY tests/TheOfficeAPI.Level3.Tests.Integration/*.csproj ./tests/TheOfficeAPI.Level3.Tests.Integration/ COPY tests/TheOfficeAPI.Tests.E2E/*.csproj ./tests/TheOfficeAPI.Tests.E2E/ +COPY tests/TheOfficeAPI.Common.Tests.Unit/*.csproj ./tests/TheOfficeAPI.Common.Tests.Unit/ # Restore dependencies RUN dotnet restore TheOfficeAPI.sln @@ -28,7 +29,7 @@ RUN dotnet publish src/TheOfficeAPI/TheOfficeAPI.csproj \ --no-restore # Runtime stage -FROM mcr.microsoft.com/dotnet/aspnet:9.0 +FROM mcr.microsoft.com/dotnet/aspnet:10.0 WORKDIR /app # Copy compiled files diff --git a/src/TheOfficeAPI/TheOfficeAPI.csproj b/src/TheOfficeAPI/TheOfficeAPI.csproj index 1238e01..15824a1 100644 --- a/src/TheOfficeAPI/TheOfficeAPI.csproj +++ b/src/TheOfficeAPI/TheOfficeAPI.csproj @@ -1,6 +1,6 @@ - net9.0 + net10.0 enable enable true diff --git a/stryker-config.json b/stryker-config.json index 37bc3a7..75acfed 100644 --- a/stryker-config.json +++ b/stryker-config.json @@ -3,7 +3,7 @@ "stryker-config": { "solution": "TheOfficeAPI.sln", "project": "src/TheOfficeAPI/TheOfficeAPI.csproj", - "target-framework": "net9.0", + "target-framework": "net10.0", "configuration": "Debug", "coverage-analysis": "perTest", "break-on-initial-test-failure": true, diff --git a/tests/TheOfficeAPI.Common.Tests.Unit/TheOfficeAPI.Common.Tests.Unit.csproj b/tests/TheOfficeAPI.Common.Tests.Unit/TheOfficeAPI.Common.Tests.Unit.csproj index d7177e9..08ca3aa 100644 --- a/tests/TheOfficeAPI.Common.Tests.Unit/TheOfficeAPI.Common.Tests.Unit.csproj +++ b/tests/TheOfficeAPI.Common.Tests.Unit/TheOfficeAPI.Common.Tests.Unit.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable false diff --git a/tests/TheOfficeAPI.Level0.Tests.Integration/TheOfficeAPI.Level0.Tests.Integration.csproj b/tests/TheOfficeAPI.Level0.Tests.Integration/TheOfficeAPI.Level0.Tests.Integration.csproj index 7eaee29..c5b505e 100644 --- a/tests/TheOfficeAPI.Level0.Tests.Integration/TheOfficeAPI.Level0.Tests.Integration.csproj +++ b/tests/TheOfficeAPI.Level0.Tests.Integration/TheOfficeAPI.Level0.Tests.Integration.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable false diff --git a/tests/TheOfficeAPI.Level0.Tests.Unit/TheOfficeAPI.Level0.Tests.Unit.csproj b/tests/TheOfficeAPI.Level0.Tests.Unit/TheOfficeAPI.Level0.Tests.Unit.csproj index e07cf42..5a165e7 100644 --- a/tests/TheOfficeAPI.Level0.Tests.Unit/TheOfficeAPI.Level0.Tests.Unit.csproj +++ b/tests/TheOfficeAPI.Level0.Tests.Unit/TheOfficeAPI.Level0.Tests.Unit.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable false diff --git a/tests/TheOfficeAPI.Level1.Tests.Integration/TheOfficeAPI.Level1.Tests.Integration.csproj b/tests/TheOfficeAPI.Level1.Tests.Integration/TheOfficeAPI.Level1.Tests.Integration.csproj index 7eaee29..c5b505e 100644 --- a/tests/TheOfficeAPI.Level1.Tests.Integration/TheOfficeAPI.Level1.Tests.Integration.csproj +++ b/tests/TheOfficeAPI.Level1.Tests.Integration/TheOfficeAPI.Level1.Tests.Integration.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable false diff --git a/tests/TheOfficeAPI.Level1.Tests.Unit/TheOfficeAPI.Level1.Tests.Unit.csproj b/tests/TheOfficeAPI.Level1.Tests.Unit/TheOfficeAPI.Level1.Tests.Unit.csproj index e07cf42..5a165e7 100644 --- a/tests/TheOfficeAPI.Level1.Tests.Unit/TheOfficeAPI.Level1.Tests.Unit.csproj +++ b/tests/TheOfficeAPI.Level1.Tests.Unit/TheOfficeAPI.Level1.Tests.Unit.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable false diff --git a/tests/TheOfficeAPI.Level2.Tests.Integration/TheOfficeAPI.Level2.Tests.Integration.csproj b/tests/TheOfficeAPI.Level2.Tests.Integration/TheOfficeAPI.Level2.Tests.Integration.csproj index 7eaee29..c5b505e 100644 --- a/tests/TheOfficeAPI.Level2.Tests.Integration/TheOfficeAPI.Level2.Tests.Integration.csproj +++ b/tests/TheOfficeAPI.Level2.Tests.Integration/TheOfficeAPI.Level2.Tests.Integration.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable false diff --git a/tests/TheOfficeAPI.Level2.Tests.Unit/TheOfficeAPI.Level2.Tests.Unit.csproj b/tests/TheOfficeAPI.Level2.Tests.Unit/TheOfficeAPI.Level2.Tests.Unit.csproj index e07cf42..5a165e7 100644 --- a/tests/TheOfficeAPI.Level2.Tests.Unit/TheOfficeAPI.Level2.Tests.Unit.csproj +++ b/tests/TheOfficeAPI.Level2.Tests.Unit/TheOfficeAPI.Level2.Tests.Unit.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable false diff --git a/tests/TheOfficeAPI.Level3.Tests.Integration/TheOfficeAPI.Level3.Tests.Integration.csproj b/tests/TheOfficeAPI.Level3.Tests.Integration/TheOfficeAPI.Level3.Tests.Integration.csproj index 7eaee29..c5b505e 100644 --- a/tests/TheOfficeAPI.Level3.Tests.Integration/TheOfficeAPI.Level3.Tests.Integration.csproj +++ b/tests/TheOfficeAPI.Level3.Tests.Integration/TheOfficeAPI.Level3.Tests.Integration.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable false diff --git a/tests/TheOfficeAPI.Level3.Tests.Unit/TheOfficeAPI.Level3.Tests.Unit.csproj b/tests/TheOfficeAPI.Level3.Tests.Unit/TheOfficeAPI.Level3.Tests.Unit.csproj index e07cf42..5a165e7 100644 --- a/tests/TheOfficeAPI.Level3.Tests.Unit/TheOfficeAPI.Level3.Tests.Unit.csproj +++ b/tests/TheOfficeAPI.Level3.Tests.Unit/TheOfficeAPI.Level3.Tests.Unit.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable false diff --git a/tests/TheOfficeAPI.Tests.E2E/TheOfficeAPI.Tests.E2E.csproj b/tests/TheOfficeAPI.Tests.E2E/TheOfficeAPI.Tests.E2E.csproj index cb3bae8..88dd8b1 100644 --- a/tests/TheOfficeAPI.Tests.E2E/TheOfficeAPI.Tests.E2E.csproj +++ b/tests/TheOfficeAPI.Tests.E2E/TheOfficeAPI.Tests.E2E.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable false From 0a86b982a9c6c30ca630b259f2fb27099e697042 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 13:28:33 +0000 Subject: [PATCH 3/7] fix(deps): bump Microsoft.Extensions.Options to 10.0.3 Fixes NU1605 package downgrade error: Hosting.Abstractions 10.0.3 transitively requires Options >= 10.0.3, but it was pinned at 10.0.2. https://claude.ai/code/session_01RNJdS4XPdZN1uFQiYvrCXu --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 9d2f61b..8079011 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -20,6 +20,6 @@ - + From 09a1f0c36f97dfa74cf9b22e7a2ac98339257ea9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 14:48:35 +0000 Subject: [PATCH 4/7] fix(build): update OpenApi namespace for Microsoft.OpenApi v2.0 In Microsoft.OpenApi v2.0 (pulled in by Swashbuckle 10.x), types like OpenApiInfo moved from Microsoft.OpenApi.Models to Microsoft.OpenApi. https://claude.ai/code/session_01RNJdS4XPdZN1uFQiYvrCXu --- .../Level0/Extensions/SwaggerConfigurationExtensions.cs | 2 +- .../Level1/Extensions/SwaggerConfigurationExtensions.cs | 2 +- .../Level2/Extensions/SwaggerConfigurationExtensions.cs | 2 +- .../Level3/Extensions/SwaggerConfigurationExtensions.cs | 2 +- src/TheOfficeAPI/Program.cs | 8 ++++---- .../SwaggerConfigurationExtensionsTests.cs | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/TheOfficeAPI/Level0/Extensions/SwaggerConfigurationExtensions.cs b/src/TheOfficeAPI/Level0/Extensions/SwaggerConfigurationExtensions.cs index 47a3cac..2b99e3d 100644 --- a/src/TheOfficeAPI/Level0/Extensions/SwaggerConfigurationExtensions.cs +++ b/src/TheOfficeAPI/Level0/Extensions/SwaggerConfigurationExtensions.cs @@ -1,5 +1,5 @@ using System.Reflection; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; namespace TheOfficeAPI.Level0.Extensions { diff --git a/src/TheOfficeAPI/Level1/Extensions/SwaggerConfigurationExtensions.cs b/src/TheOfficeAPI/Level1/Extensions/SwaggerConfigurationExtensions.cs index 9ee23e3..755461e 100644 --- a/src/TheOfficeAPI/Level1/Extensions/SwaggerConfigurationExtensions.cs +++ b/src/TheOfficeAPI/Level1/Extensions/SwaggerConfigurationExtensions.cs @@ -1,5 +1,5 @@ using System.Reflection; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; namespace TheOfficeAPI.Level1.Extensions { diff --git a/src/TheOfficeAPI/Level2/Extensions/SwaggerConfigurationExtensions.cs b/src/TheOfficeAPI/Level2/Extensions/SwaggerConfigurationExtensions.cs index a3473f8..647cd18 100644 --- a/src/TheOfficeAPI/Level2/Extensions/SwaggerConfigurationExtensions.cs +++ b/src/TheOfficeAPI/Level2/Extensions/SwaggerConfigurationExtensions.cs @@ -1,5 +1,5 @@ using System.Reflection; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; namespace TheOfficeAPI.Level2.Extensions { diff --git a/src/TheOfficeAPI/Level3/Extensions/SwaggerConfigurationExtensions.cs b/src/TheOfficeAPI/Level3/Extensions/SwaggerConfigurationExtensions.cs index 8792805..bf8c36b 100644 --- a/src/TheOfficeAPI/Level3/Extensions/SwaggerConfigurationExtensions.cs +++ b/src/TheOfficeAPI/Level3/Extensions/SwaggerConfigurationExtensions.cs @@ -1,5 +1,5 @@ using System.Reflection; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; namespace TheOfficeAPI.Level3.Extensions { diff --git a/src/TheOfficeAPI/Program.cs b/src/TheOfficeAPI/Program.cs index b82be18..1f98a1a 100644 --- a/src/TheOfficeAPI/Program.cs +++ b/src/TheOfficeAPI/Program.cs @@ -66,28 +66,28 @@ public static WebApplication CreateWebApplication(string[] args) builder.Services.AddSwaggerGen(c => { // Register all API versions - c.SwaggerDoc("v0", new Microsoft.OpenApi.Models.OpenApiInfo + c.SwaggerDoc("v0", new Microsoft.OpenApi.OpenApiInfo { Title = "The Office API - Level 0", Version = "v0", Description = "Richardson Maturity Model Level 0 implementation" }); - c.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo + c.SwaggerDoc("v1", new Microsoft.OpenApi.OpenApiInfo { Title = "The Office API - Level 1", Version = "v1", Description = "Richardson Maturity Model Level 1 implementation - Introduces resource-based URIs" }); - c.SwaggerDoc("v2", new Microsoft.OpenApi.Models.OpenApiInfo + c.SwaggerDoc("v2", new Microsoft.OpenApi.OpenApiInfo { Title = "The Office API - Level 2", Version = "v2", Description = "Richardson Maturity Model Level 2 implementation - Introduces HTTP verbs and proper status codes" }); - c.SwaggerDoc("v3", new Microsoft.OpenApi.Models.OpenApiInfo + c.SwaggerDoc("v3", new Microsoft.OpenApi.OpenApiInfo { Title = "The Office API - Level 3", Version = "v3", diff --git a/tests/TheOfficeAPI.Common.Tests.Unit/SwaggerConfigurationExtensionsTests.cs b/tests/TheOfficeAPI.Common.Tests.Unit/SwaggerConfigurationExtensionsTests.cs index ecfd292..a2f76cf 100644 --- a/tests/TheOfficeAPI.Common.Tests.Unit/SwaggerConfigurationExtensionsTests.cs +++ b/tests/TheOfficeAPI.Common.Tests.Unit/SwaggerConfigurationExtensionsTests.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using Swashbuckle.AspNetCore.SwaggerGen; namespace TheOfficeAPI.Common.Tests.Unit; From 18ea4e66fe55680b20f8672ce9d19478be7f2459 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 14:58:30 +0000 Subject: [PATCH 5/7] fix(quality): resolve SonarCloud code quality issues - RSPEC-6968: Add return types to ProducesResponseType attributes on all HealthController endpoints and Level1 SeasonsController - RSPEC-1192: Extract "Healthy" string literal to constant in HealthCheckService, extract "/api/v3/seasons" to SeasonsBasePath constant in Level3 EpisodesController - RSPEC-1118: Add protected constructor to Program class (utility class) https://claude.ai/code/session_01RNJdS4XPdZN1uFQiYvrCXu --- .../Common/Services/HealthCheckService.cs | 12 +++--- .../Level0/Controllers/HealthController.cs | 7 ++-- .../Level1/Controllers/HealthController.cs | 7 ++-- .../Level1/Controllers/SeasonsController.cs | 1 + .../Level2/Controllers/HealthController.cs | 7 ++-- .../Level3/Controllers/EpisodesController.cs | 41 ++++++++++--------- .../Level3/Controllers/HealthController.cs | 7 ++-- src/TheOfficeAPI/Program.cs | 2 + 8 files changed, 47 insertions(+), 37 deletions(-) diff --git a/src/TheOfficeAPI/Common/Services/HealthCheckService.cs b/src/TheOfficeAPI/Common/Services/HealthCheckService.cs index 214d35c..4d7de9a 100644 --- a/src/TheOfficeAPI/Common/Services/HealthCheckService.cs +++ b/src/TheOfficeAPI/Common/Services/HealthCheckService.cs @@ -8,6 +8,8 @@ namespace TheOfficeAPI.Common.Services; /// public class HealthCheckService { + private const string HealthyStatus = HealthyStatus; + private readonly DateTime _startTime; private readonly string _version; @@ -24,7 +26,7 @@ public HealthCheckResponse GetLivenessStatus() { return new HealthCheckResponse { - Status = "Healthy", + Status = HealthyStatus, Timestamp = DateTime.UtcNow, Message = "Application is alive" }; @@ -37,7 +39,7 @@ public DetailedHealthCheckResponse GetReadinessStatus() { var response = new DetailedHealthCheckResponse { - Status = "Healthy", + Status = HealthyStatus, Timestamp = DateTime.UtcNow, Message = "Application is ready to serve traffic", Uptime = DateTime.UtcNow - _startTime, @@ -46,7 +48,7 @@ public DetailedHealthCheckResponse GetReadinessStatus() { ["application"] = new ComponentHealth { - Status = "Healthy", + Status = HealthyStatus, Description = "Application is running normally", Data = new Dictionary { @@ -56,7 +58,7 @@ public DetailedHealthCheckResponse GetReadinessStatus() }, ["dataService"] = new ComponentHealth { - Status = "Healthy", + Status = HealthyStatus, Description = "In-memory data service is available", Data = new Dictionary { @@ -77,7 +79,7 @@ public HealthCheckResponse GetHealthStatus() { return new HealthCheckResponse { - Status = "Healthy", + Status = HealthyStatus, Timestamp = DateTime.UtcNow, Message = "OK" }; diff --git a/src/TheOfficeAPI/Level0/Controllers/HealthController.cs b/src/TheOfficeAPI/Level0/Controllers/HealthController.cs index 27b2669..e763550 100644 --- a/src/TheOfficeAPI/Level0/Controllers/HealthController.cs +++ b/src/TheOfficeAPI/Level0/Controllers/HealthController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using TheOfficeAPI.Common.Models; using TheOfficeAPI.Common.Services; namespace TheOfficeAPI.Level0.Controllers; @@ -23,7 +24,7 @@ public HealthController(HealthCheckService healthCheckService) /// Health status /// Service is healthy [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(HealthCheckResponse), StatusCodes.Status200OK)] public IActionResult Get() { var health = _healthCheckService.GetHealthStatus(); @@ -40,7 +41,7 @@ public IActionResult Get() /// is still running. If this fails, the container should be restarted. /// [HttpGet("live")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(HealthCheckResponse), StatusCodes.Status200OK)] public IActionResult GetLiveness() { var health = _healthCheckService.GetLivenessStatus(); @@ -57,7 +58,7 @@ public IActionResult GetLiveness() /// is ready to receive traffic. If this fails, traffic should not be routed to this instance. /// [HttpGet("ready")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(DetailedHealthCheckResponse), StatusCodes.Status200OK)] public IActionResult GetReadiness() { var health = _healthCheckService.GetReadinessStatus(); diff --git a/src/TheOfficeAPI/Level1/Controllers/HealthController.cs b/src/TheOfficeAPI/Level1/Controllers/HealthController.cs index 20d5c65..bb1793e 100644 --- a/src/TheOfficeAPI/Level1/Controllers/HealthController.cs +++ b/src/TheOfficeAPI/Level1/Controllers/HealthController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using TheOfficeAPI.Common.Models; using TheOfficeAPI.Common.Services; namespace TheOfficeAPI.Level1.Controllers; @@ -23,7 +24,7 @@ public HealthController(HealthCheckService healthCheckService) /// Health status /// Service is healthy [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(HealthCheckResponse), StatusCodes.Status200OK)] public IActionResult Get() { var health = _healthCheckService.GetHealthStatus(); @@ -40,7 +41,7 @@ public IActionResult Get() /// is still running. If this fails, the container should be restarted. /// [HttpGet("live")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(HealthCheckResponse), StatusCodes.Status200OK)] public IActionResult GetLiveness() { var health = _healthCheckService.GetLivenessStatus(); @@ -57,7 +58,7 @@ public IActionResult GetLiveness() /// is ready to receive traffic. If this fails, traffic should not be routed to this instance. /// [HttpGet("ready")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(DetailedHealthCheckResponse), StatusCodes.Status200OK)] public IActionResult GetReadiness() { var health = _healthCheckService.GetReadinessStatus(); diff --git a/src/TheOfficeAPI/Level1/Controllers/SeasonsController.cs b/src/TheOfficeAPI/Level1/Controllers/SeasonsController.cs index 8391e97..7f9e795 100644 --- a/src/TheOfficeAPI/Level1/Controllers/SeasonsController.cs +++ b/src/TheOfficeAPI/Level1/Controllers/SeasonsController.cs @@ -50,6 +50,7 @@ public SeasonsController(TheOfficeService theOfficeService) /// /// [HttpPost] + [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public IActionResult GetAllSeasons() { try diff --git a/src/TheOfficeAPI/Level2/Controllers/HealthController.cs b/src/TheOfficeAPI/Level2/Controllers/HealthController.cs index 8b1c3ce..616fc41 100644 --- a/src/TheOfficeAPI/Level2/Controllers/HealthController.cs +++ b/src/TheOfficeAPI/Level2/Controllers/HealthController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using TheOfficeAPI.Common.Models; using TheOfficeAPI.Common.Services; namespace TheOfficeAPI.Level2.Controllers; @@ -23,7 +24,7 @@ public HealthController(HealthCheckService healthCheckService) /// Health status /// Service is healthy [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(HealthCheckResponse), StatusCodes.Status200OK)] public IActionResult Get() { var health = _healthCheckService.GetHealthStatus(); @@ -40,7 +41,7 @@ public IActionResult Get() /// is still running. If this fails, the container should be restarted. /// [HttpGet("live")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(HealthCheckResponse), StatusCodes.Status200OK)] public IActionResult GetLiveness() { var health = _healthCheckService.GetLivenessStatus(); @@ -57,7 +58,7 @@ public IActionResult GetLiveness() /// is ready to receive traffic. If this fails, traffic should not be routed to this instance. /// [HttpGet("ready")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(DetailedHealthCheckResponse), StatusCodes.Status200OK)] public IActionResult GetReadiness() { var health = _healthCheckService.GetReadinessStatus(); diff --git a/src/TheOfficeAPI/Level3/Controllers/EpisodesController.cs b/src/TheOfficeAPI/Level3/Controllers/EpisodesController.cs index cb02fca..cc9f7e8 100644 --- a/src/TheOfficeAPI/Level3/Controllers/EpisodesController.cs +++ b/src/TheOfficeAPI/Level3/Controllers/EpisodesController.cs @@ -10,6 +10,7 @@ public class EpisodesController : ControllerBase { private const string RelCollection = "collection"; private const string RelSeason = "season"; + private const string SeasonsBasePath = "/api/v3/seasons"; private readonly TheOfficeService _theOfficeService; @@ -80,8 +81,8 @@ public IActionResult GetSeasonEpisodes([FromRoute] int seasonNumber) ReleasedDate = e.ReleasedDate, Links = new List { - new Link { Rel = "self", Href = $"/api/v3/seasons/{seasonNumber}/episodes/{e.EpisodeNumber}", Method = "GET" }, - new Link { Rel = RelSeason, Href = $"/api/v3/seasons/{seasonNumber}", Method = "GET" } + new Link { Rel = "self", Href = $"{SeasonsBasePath}/{seasonNumber}/episodes/{e.EpisodeNumber}", Method = "GET" }, + new Link { Rel = RelSeason, Href = $"{SeasonsBasePath}/{seasonNumber}", Method = "GET" } } }).ToList(); @@ -92,9 +93,9 @@ public IActionResult GetSeasonEpisodes([FromRoute] int seasonNumber) Message = $"Episodes for season {seasonNumber} retrieved successfully", Links = new List { - new Link { Rel = "self", Href = $"/api/v3/seasons/{seasonNumber}/episodes", Method = "GET" }, - new Link { Rel = RelSeason, Href = $"/api/v3/seasons/{seasonNumber}", Method = "GET" }, - new Link { Rel = RelCollection, Href = "/api/v3/seasons", Method = "GET" } + new Link { Rel = "self", Href = $"{SeasonsBasePath}/{seasonNumber}/episodes", Method = "GET" }, + new Link { Rel = RelSeason, Href = $"{SeasonsBasePath}/{seasonNumber}", Method = "GET" }, + new Link { Rel = RelCollection, Href = SeasonsBasePath, Method = "GET" } } }; @@ -191,9 +192,9 @@ public IActionResult GetEpisode([FromRoute] int seasonNumber, [FromRoute] int ep Message = "Episode not found", Links = new List { - new Link { Rel = "episodes", Href = $"/api/v3/seasons/{seasonNumber}/episodes", Method = "GET" }, - new Link { Rel = RelSeason, Href = $"/api/v3/seasons/{seasonNumber}", Method = "GET" }, - new Link { Rel = RelCollection, Href = "/api/v3/seasons", Method = "GET" } + new Link { Rel = "episodes", Href = $"{SeasonsBasePath}/{seasonNumber}/episodes", Method = "GET" }, + new Link { Rel = RelSeason, Href = $"{SeasonsBasePath}/{seasonNumber}", Method = "GET" }, + new Link { Rel = RelCollection, Href = SeasonsBasePath, Method = "GET" } } }); } @@ -206,7 +207,7 @@ public IActionResult GetEpisode([FromRoute] int seasonNumber, [FromRoute] int ep ReleasedDate = episode.ReleasedDate, Links = new List { - new Link { Rel = "self", Href = $"/api/v3/seasons/{seasonNumber}/episodes/{episodeNumber}", Method = "GET" } + new Link { Rel = "self", Href = $"{SeasonsBasePath}/{seasonNumber}/episodes/{episodeNumber}", Method = "GET" } } }; @@ -217,7 +218,7 @@ public IActionResult GetEpisode([FromRoute] int seasonNumber, [FromRoute] int ep episodeResource.Links.Add(new Link { Rel = "next", - Href = $"/api/v3/seasons/{seasonNumber}/episodes/{episodeNumber + 1}", + Href = $"{SeasonsBasePath}/{seasonNumber}/episodes/{episodeNumber + 1}", Method = "GET" }); } @@ -228,15 +229,15 @@ public IActionResult GetEpisode([FromRoute] int seasonNumber, [FromRoute] int ep episodeResource.Links.Add(new Link { Rel = "previous", - Href = $"/api/v3/seasons/{seasonNumber}/episodes/{episodeNumber - 1}", + Href = $"{SeasonsBasePath}/{seasonNumber}/episodes/{episodeNumber - 1}", Method = "GET" }); } // Add parent and collection links - episodeResource.Links.Add(new Link { Rel = RelSeason, Href = $"/api/v3/seasons/{seasonNumber}", Method = "GET" }); - episodeResource.Links.Add(new Link { Rel = "episodes", Href = $"/api/v3/seasons/{seasonNumber}/episodes", Method = "GET" }); - episodeResource.Links.Add(new Link { Rel = RelCollection, Href = "/api/v3/seasons", Method = "GET" }); + episodeResource.Links.Add(new Link { Rel = RelSeason, Href = $"{SeasonsBasePath}/{seasonNumber}", Method = "GET" }); + episodeResource.Links.Add(new Link { Rel = "episodes", Href = $"{SeasonsBasePath}/{seasonNumber}/episodes", Method = "GET" }); + episodeResource.Links.Add(new Link { Rel = RelCollection, Href = SeasonsBasePath, Method = "GET" }); var response = new HateoasResponse { @@ -245,8 +246,8 @@ public IActionResult GetEpisode([FromRoute] int seasonNumber, [FromRoute] int ep Message = "Episode retrieved successfully", Links = new List { - new Link { Rel = "self", Href = $"/api/v3/seasons/{seasonNumber}/episodes/{episodeNumber}", Method = "GET" }, - new Link { Rel = RelCollection, Href = "/api/v3/seasons", Method = "GET" } + new Link { Rel = "self", Href = $"{SeasonsBasePath}/{seasonNumber}/episodes/{episodeNumber}", Method = "GET" }, + new Link { Rel = RelCollection, Href = SeasonsBasePath, Method = "GET" } } }; @@ -275,7 +276,7 @@ public IActionResult GetEpisode([FromRoute] int seasonNumber, [FromRoute] int ep Message = "Invalid request", Links = new List { - new Link { Rel = RelCollection, Href = "/api/v3/seasons", Method = "GET" } + new Link { Rel = RelCollection, Href = SeasonsBasePath, Method = "GET" } } }); } @@ -294,9 +295,9 @@ public IActionResult GetEpisode([FromRoute] int seasonNumber, [FromRoute] int ep Message = "Invalid request", Links = new List { - new Link { Rel = "episodes", Href = $"/api/v3/seasons/{season}/episodes", Method = "GET" }, - new Link { Rel = RelSeason, Href = $"/api/v3/seasons/{season}", Method = "GET" }, - new Link { Rel = RelCollection, Href = "/api/v3/seasons", Method = "GET" } + new Link { Rel = "episodes", Href = $"{SeasonsBasePath}/{season}/episodes", Method = "GET" }, + new Link { Rel = RelSeason, Href = $"{SeasonsBasePath}/{season}", Method = "GET" }, + new Link { Rel = RelCollection, Href = SeasonsBasePath, Method = "GET" } } }); } diff --git a/src/TheOfficeAPI/Level3/Controllers/HealthController.cs b/src/TheOfficeAPI/Level3/Controllers/HealthController.cs index 9970866..0fd3ba1 100644 --- a/src/TheOfficeAPI/Level3/Controllers/HealthController.cs +++ b/src/TheOfficeAPI/Level3/Controllers/HealthController.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using TheOfficeAPI.Common.Models; using TheOfficeAPI.Common.Services; namespace TheOfficeAPI.Level3.Controllers; @@ -23,7 +24,7 @@ public HealthController(HealthCheckService healthCheckService) /// Health status /// Service is healthy [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(HealthCheckResponse), StatusCodes.Status200OK)] public IActionResult Get() { var health = _healthCheckService.GetHealthStatus(); @@ -40,7 +41,7 @@ public IActionResult Get() /// is still running. If this fails, the container should be restarted. /// [HttpGet("live")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(HealthCheckResponse), StatusCodes.Status200OK)] public IActionResult GetLiveness() { var health = _healthCheckService.GetLivenessStatus(); @@ -57,7 +58,7 @@ public IActionResult GetLiveness() /// is ready to receive traffic. If this fails, traffic should not be routed to this instance. /// [HttpGet("ready")] - [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(DetailedHealthCheckResponse), StatusCodes.Status200OK)] public IActionResult GetReadiness() { var health = _healthCheckService.GetReadinessStatus(); diff --git a/src/TheOfficeAPI/Program.cs b/src/TheOfficeAPI/Program.cs index 1f98a1a..2688811 100644 --- a/src/TheOfficeAPI/Program.cs +++ b/src/TheOfficeAPI/Program.cs @@ -7,6 +7,8 @@ namespace TheOfficeAPI; public class Program { + protected Program() { } + public static void Main(string[] args) { CreateWebApplication(args).Run(); From 9b7501905b3c1213a8c514343fdf0c8cd1b64f18 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 15:08:35 +0000 Subject: [PATCH 6/7] fix(build): fix circular constant definition in HealthCheckService The HealthyStatus constant was self-referencing (HealthyStatus = HealthyStatus) instead of being assigned the string literal "Healthy". https://claude.ai/code/session_01RNJdS4XPdZN1uFQiYvrCXu --- src/TheOfficeAPI/Common/Services/HealthCheckService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TheOfficeAPI/Common/Services/HealthCheckService.cs b/src/TheOfficeAPI/Common/Services/HealthCheckService.cs index 4d7de9a..9f18f0f 100644 --- a/src/TheOfficeAPI/Common/Services/HealthCheckService.cs +++ b/src/TheOfficeAPI/Common/Services/HealthCheckService.cs @@ -8,7 +8,7 @@ namespace TheOfficeAPI.Common.Services; /// public class HealthCheckService { - private const string HealthyStatus = HealthyStatus; + private const string HealthyStatus = "Healthy"; private readonly DateTime _startTime; private readonly string _version; From 6115186a8177f4e6ffc0ab866bcf5e9494312108 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 15:17:28 +0000 Subject: [PATCH 7/7] fix(quality): resolve second batch of SonarCloud code quality issues - RSPEC-2701: Replace Assert.Equal(true, ...) with Assert.True() in HealthCheckServiceTests - RSPEC-2925: Replace Thread.Sleep with async Task.Delay in HealthCheckServiceTests - RSPEC-6968: Add typed ProducesResponseType to Level0/OfficeApiController and Level1/EpisodesController - RSPEC-3776: Reduce cognitive complexity in Program.cs by extracting DetermineBindingUrl and ConfigureBasicServices - RSPEC-1075/RSPEC-5332: Extract hardcoded URIs to helper method with NOSONAR comments https://claude.ai/code/session_01RNJdS4XPdZN1uFQiYvrCXu --- .../Level0/Controllers/OfficeApiController.cs | 1 + .../Level1/Controllers/EpisodesController.cs | 2 + src/TheOfficeAPI/Program.cs | 179 +++++++++--------- .../HealthCheckServiceTests.cs | 14 +- 4 files changed, 101 insertions(+), 95 deletions(-) diff --git a/src/TheOfficeAPI/Level0/Controllers/OfficeApiController.cs b/src/TheOfficeAPI/Level0/Controllers/OfficeApiController.cs index 00c0dfa..750a70c 100644 --- a/src/TheOfficeAPI/Level0/Controllers/OfficeApiController.cs +++ b/src/TheOfficeAPI/Level0/Controllers/OfficeApiController.cs @@ -118,6 +118,7 @@ public Level0Controller(TheOfficeService theOfficeService) /// /// [HttpPost("theOffice")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public IActionResult HandleRequest([FromBody] ApiRequest request) { // Level 0: Always return 200 OK, put actual status in response body diff --git a/src/TheOfficeAPI/Level1/Controllers/EpisodesController.cs b/src/TheOfficeAPI/Level1/Controllers/EpisodesController.cs index b8f90b3..2159e69 100644 --- a/src/TheOfficeAPI/Level1/Controllers/EpisodesController.cs +++ b/src/TheOfficeAPI/Level1/Controllers/EpisodesController.cs @@ -49,6 +49,7 @@ public EpisodesController(TheOfficeService theOfficeService) /// /// [HttpPost] + [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)] public IActionResult GetSeasonEpisodes([FromRoute] int seasonNumber) { try @@ -108,6 +109,7 @@ public IActionResult GetSeasonEpisodes([FromRoute] int seasonNumber) /// /// [HttpPost("{episodeNumber}")] + [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] public IActionResult GetEpisode([FromRoute] int seasonNumber, [FromRoute] int episodeNumber) { try diff --git a/src/TheOfficeAPI/Program.cs b/src/TheOfficeAPI/Program.cs index 2688811..3479bdc 100644 --- a/src/TheOfficeAPI/Program.cs +++ b/src/TheOfficeAPI/Program.cs @@ -28,30 +28,11 @@ public static WebApplication CreateWebApplication(string[] args) var environmentOptions = builder.Configuration.GetSection(EnvironmentOptions.SectionName).Get(); - // RAILWAY: Use Railway's PORT environment variable and bind to 0.0.0.0 - var port = Environment.GetEnvironmentVariable("PORT"); - string url; - - if (port != null) - { - url = $"http://0.0.0.0:{port}"; - Console.WriteLine($"=== RAILWAY/PRODUCTION MODE ==="); - Console.WriteLine($"PORT from environment: {port}"); - Console.WriteLine($"Binding to: {url}"); - Console.WriteLine($"================================"); - } - else - { - url = serverOptions?.DefaultUrl ?? "http://localhost:5000"; - Console.WriteLine($"=== LOCAL DEVELOPMENT MODE ==="); - Console.WriteLine($"Using config URL: {url}"); - Console.WriteLine($"================================"); - } - + var url = DetermineBindingUrl(serverOptions); builder.WebHost.UseUrls(url); var maturityLevel = DetermineMaturityLevel(environmentOptions?.MaturityLevelVariable ?? "MATURITY_LEVEL"); - var hasMaturityLevel = maturityLevel == MaturityLevel.Level0 || maturityLevel == MaturityLevel.Level1 || maturityLevel == MaturityLevel.Level2 || maturityLevel == MaturityLevel.Level3; + var hasMaturityLevel = maturityLevel != null; if (hasMaturityLevel) { @@ -61,73 +42,7 @@ public static WebApplication CreateWebApplication(string[] args) } else { - Console.WriteLine("Starting with basic configuration..."); - builder.Services.AddSingleton(); - builder.Services.AddControllers(); - builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwaggerGen(c => - { - // Register all API versions - c.SwaggerDoc("v0", new Microsoft.OpenApi.OpenApiInfo - { - Title = "The Office API - Level 0", - Version = "v0", - Description = "Richardson Maturity Model Level 0 implementation" - }); - - c.SwaggerDoc("v1", new Microsoft.OpenApi.OpenApiInfo - { - Title = "The Office API - Level 1", - Version = "v1", - Description = "Richardson Maturity Model Level 1 implementation - Introduces resource-based URIs" - }); - - c.SwaggerDoc("v2", new Microsoft.OpenApi.OpenApiInfo - { - Title = "The Office API - Level 2", - Version = "v2", - Description = "Richardson Maturity Model Level 2 implementation - Introduces HTTP verbs and proper status codes" - }); - - c.SwaggerDoc("v3", new Microsoft.OpenApi.OpenApiInfo - { - Title = "The Office API - Level 3", - Version = "v3", - Description = "Richardson Maturity Model Level 3 implementation - HATEOAS with hypermedia links" - }); - - // Filter controllers by namespace for each version - c.DocInclusionPredicate((docName, apiDesc) => - { - var controllerActionDescriptor = apiDesc.ActionDescriptor as Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor; - if (controllerActionDescriptor == null) return false; - - var controllerNamespace = controllerActionDescriptor.ControllerTypeInfo.Namespace ?? string.Empty; - - return docName switch - { - "v0" => controllerNamespace.StartsWith("TheOfficeAPI.Level0"), - "v1" => controllerNamespace.StartsWith("TheOfficeAPI.Level1"), - "v2" => controllerNamespace.StartsWith("TheOfficeAPI.Level2"), - "v3" => controllerNamespace.StartsWith("TheOfficeAPI.Level3"), - _ => false - }; - }); - - // Include XML comments if available - var xmlFile = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml"; - var xmlPath = System.IO.Path.Combine(AppContext.BaseDirectory, xmlFile); - if (System.IO.File.Exists(xmlPath)) - { - c.IncludeXmlComments(xmlPath); - } - }); - - // Register services for all levels to support all API versions - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + ConfigureBasicServices(builder); } var app = builder.Build(); @@ -144,6 +59,94 @@ public static WebApplication CreateWebApplication(string[] args) return app; } + private static string DetermineBindingUrl(ServerOptions? serverOptions) + { + var port = Environment.GetEnvironmentVariable("PORT"); + + if (port != null) + { + var url = FormattableString.Invariant($"http://0.0.0.0:{port}"); // NOSONAR - internal container binding, TLS terminated at load balancer + Console.WriteLine("=== RAILWAY/PRODUCTION MODE ==="); + Console.WriteLine($"PORT from environment: {port}"); + Console.WriteLine($"Binding to: {url}"); + Console.WriteLine("================================"); + return url; + } + + var defaultUrl = serverOptions?.DefaultUrl ?? "http://localhost:5000"; // NOSONAR - local development only + Console.WriteLine("=== LOCAL DEVELOPMENT MODE ==="); + Console.WriteLine($"Using config URL: {defaultUrl}"); + Console.WriteLine("================================"); + return defaultUrl; + } + + private static void ConfigureBasicServices(WebApplicationBuilder builder) + { + Console.WriteLine("Starting with basic configuration..."); + builder.Services.AddSingleton(); + builder.Services.AddControllers(); + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(c => + { + c.SwaggerDoc("v0", new Microsoft.OpenApi.OpenApiInfo + { + Title = "The Office API - Level 0", + Version = "v0", + Description = "Richardson Maturity Model Level 0 implementation" + }); + + c.SwaggerDoc("v1", new Microsoft.OpenApi.OpenApiInfo + { + Title = "The Office API - Level 1", + Version = "v1", + Description = "Richardson Maturity Model Level 1 implementation - Introduces resource-based URIs" + }); + + c.SwaggerDoc("v2", new Microsoft.OpenApi.OpenApiInfo + { + Title = "The Office API - Level 2", + Version = "v2", + Description = "Richardson Maturity Model Level 2 implementation - Introduces HTTP verbs and proper status codes" + }); + + c.SwaggerDoc("v3", new Microsoft.OpenApi.OpenApiInfo + { + Title = "The Office API - Level 3", + Version = "v3", + Description = "Richardson Maturity Model Level 3 implementation - HATEOAS with hypermedia links" + }); + + c.DocInclusionPredicate((docName, apiDesc) => + { + var controllerActionDescriptor = apiDesc.ActionDescriptor as Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor; + if (controllerActionDescriptor == null) return false; + + var controllerNamespace = controllerActionDescriptor.ControllerTypeInfo.Namespace ?? string.Empty; + + return docName switch + { + "v0" => controllerNamespace.StartsWith("TheOfficeAPI.Level0"), + "v1" => controllerNamespace.StartsWith("TheOfficeAPI.Level1"), + "v2" => controllerNamespace.StartsWith("TheOfficeAPI.Level2"), + "v3" => controllerNamespace.StartsWith("TheOfficeAPI.Level3"), + _ => false + }; + }); + + var xmlFile = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = System.IO.Path.Combine(AppContext.BaseDirectory, xmlFile); + if (System.IO.File.Exists(xmlPath)) + { + c.IncludeXmlComments(xmlPath); + } + }); + + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + } + private static MaturityLevel? DetermineMaturityLevel(string environmentVariable) { var maturityLevelString = Environment.GetEnvironmentVariable(environmentVariable); diff --git a/tests/TheOfficeAPI.Common.Tests.Unit/HealthCheckServiceTests.cs b/tests/TheOfficeAPI.Common.Tests.Unit/HealthCheckServiceTests.cs index 0389f67..359a65c 100644 --- a/tests/TheOfficeAPI.Common.Tests.Unit/HealthCheckServiceTests.cs +++ b/tests/TheOfficeAPI.Common.Tests.Unit/HealthCheckServiceTests.cs @@ -83,11 +83,11 @@ public void GetReadinessStatus_IncludesVersion() } [Fact] - public void GetReadinessStatus_IncludesUptime() + public async Task GetReadinessStatus_IncludesUptime() { // Arrange var service = new HealthCheckService(); - Thread.Sleep(100); // Wait a bit to ensure uptime is > 0 + await Task.Delay(100); // Wait a bit to ensure uptime is > 0 // Act var result = service.GetReadinessStatus(); @@ -138,18 +138,18 @@ public void GetReadinessStatus_IncludesDataServiceComponent() Assert.True(dataComponent.Data.ContainsKey("type")); Assert.Equal("In-Memory", dataComponent.Data["type"]); Assert.True(dataComponent.Data.ContainsKey("initialized")); - Assert.Equal(true, dataComponent.Data["initialized"]); + Assert.True((bool)dataComponent.Data["initialized"]); } [Fact] - public void GetReadinessStatus_UptimeIncreasesOverTime() + public async Task GetReadinessStatus_UptimeIncreasesOverTime() { // Arrange var service = new HealthCheckService(); // Act var result1 = service.GetReadinessStatus(); - Thread.Sleep(50); + await Task.Delay(50); var result2 = service.GetReadinessStatus(); // Assert @@ -157,11 +157,11 @@ public void GetReadinessStatus_UptimeIncreasesOverTime() } [Fact] - public void MultipleInstances_HaveIndependentStartTimes() + public async Task MultipleInstances_HaveIndependentStartTimes() { // Arrange & Act var service1 = new HealthCheckService(); - Thread.Sleep(50); + await Task.Delay(50); var service2 = new HealthCheckService(); var result1 = service1.GetReadinessStatus();