Skip to content

Commit 39ef40e

Browse files
chore: update health check endpoints and tests to include live and ready checks
1 parent 2bf45d0 commit 39ef40e

12 files changed

Lines changed: 70 additions & 32 deletions

File tree

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Specs are served at runtime: `/openapi/v1.0.json` (V1 host), `/openapi/v2.0.json
3333
## Key Endpoints
3434

3535
- **`ApiInfoController`** — returns `AssemblyInformationalVersion` at `/v1.0/info` (V1 host) and `/v2.0/info` (V2 host). Anonymous access. Used by deploy workflows for version verification.
36-
- **`HealthController`** — returns health check status at `/v1.0/health` (V1 host) and `/v2.0/health` (V2 host). Anonymous access.
36+
- **`HealthController`** — returns health check status at `/v1.0/health/live` and `/v1.0/health/ready` (V1 host), and `/v2.0/health/live` and `/v2.0/health/ready` (V2 host). Anonymous access.
3737
- **Root `/`** — minimal API endpoint (`app.MapGet`) returning 200 OK for App Service wake-up. Excluded from OpenAPI spec.
3838

3939
## Data Access

docs/api-versioning.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Both hosts use identical patterns:
1515
- **Version reader**: `UrlSegmentApiVersionReader` extracts the version from the URL path
1616
- **Group name format**: `'v'VV` — always includes the minor version (`v1.0`, `v2.0`) to ensure unambiguous OpenAPI document grouping
1717
- **Info endpoint**: `ApiInfoController` returns `AssemblyInformationalVersion` at `/v1.0/info` (V1 host) and `/v2.0/info` (V2 host) with anonymous access
18-
- **Health endpoint**: `HealthController` exposes `/v1.0/health` (V1 host) and `/v2.0/health` (V2 host) with anonymous access
18+
- **Health endpoints**: `HealthController` exposes `/v1.0/health/live` and `/v1.0/health/ready` (V1 host), and `/v2.0/health/live` and `/v2.0/health/ready` (V2 host), all with anonymous access
1919

2020
## OpenAPI Spec Generation
2121

@@ -57,14 +57,14 @@ The project uses **Nerdbank.GitVersioning** (`version.json` at repo root) for de
5757

5858
The API definitions are imported via `az apim api import` after the App Services are deployed. All three specs are imported:
5959

60-
| Parameter | v1 | v2 |
61-
|---|---|---|
62-
| `--api-id` | `repository-api-v1` | `repository-api-v2` |
63-
| `--api-version` | `v1` | `v2` |
64-
| `--api-version-set-id` | `repository-api` | `repository-api` |
65-
| `--specification-url` | `...v1-host/openapi/v1.0.json` | `...v2-host/openapi/v2.0.json` |
66-
| `--service-url` | `...v1-host/v1` | `...v2-host/v2` |
67-
| `--path` | `repository` | `repository` |
60+
| Parameter | v1 | v2 |
61+
| ---------------------- | ------------------------------ | ------------------------------ |
62+
| `--api-id` | `repository-api-v1` | `repository-api-v2` |
63+
| `--api-version` | `v1` | `v2` |
64+
| `--api-version-set-id` | `repository-api` | `repository-api` |
65+
| `--specification-url` | `...v1-host/openapi/v1.0.json` | `...v2-host/openapi/v2.0.json` |
66+
| `--service-url` | `...v1-host/v1` | `...v2-host/v2` |
67+
| `--path` | `repository` | `repository` |
6868

6969
All APIs share the same `--path` and version set — APIM requires this for segment versioning to work. The `--service-url` routes v1.x requests to the V1 App Service and v2.x requests to the V2 App Service.
7070

src/XtremeIdiots.Portal.Repository.Api.IntegrationTests.V1/InfoAndHealthTests.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,23 @@ public async Task GetInfo_ReturnsOkWithVersionInfo()
3939
}
4040

4141
[Fact]
42-
public async Task GetHealth_ReturnsResponse()
42+
public async Task GetHealthReady_ReturnsResponse()
4343
{
44-
var response = await _client.GetAsync("/v1.0/health");
44+
var response = await _client.GetAsync("/v1.0/health/ready");
4545

4646
Assert.True(
4747
response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.ServiceUnavailable,
4848
$"Expected OK or ServiceUnavailable but got {response.StatusCode}");
4949
}
5050

51+
[Fact]
52+
public async Task GetHealthLive_ReturnsOk()
53+
{
54+
var response = await _client.GetAsync("/v1.0/health/live");
55+
56+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
57+
}
58+
5159
[Fact]
5260
public async Task Root_ReturnsOk()
5361
{

src/XtremeIdiots.Portal.Repository.Api.IntegrationTests.V2/InfoAndHealthTests.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,23 @@ public async Task GetInfo_ReturnsOkWithVersionInfo()
3939
}
4040

4141
[Fact]
42-
public async Task GetHealth_ReturnsResponse()
42+
public async Task GetHealthReady_ReturnsResponse()
4343
{
44-
var response = await _client.GetAsync("/v2.0/health");
44+
var response = await _client.GetAsync("/v2.0/health/ready");
4545

4646
Assert.True(
4747
response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.ServiceUnavailable,
4848
$"Expected OK or ServiceUnavailable but got {response.StatusCode}");
4949
}
5050

51+
[Fact]
52+
public async Task GetHealthLive_ReturnsOk()
53+
{
54+
var response = await _client.GetAsync("/v2.0/health/live");
55+
56+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
57+
}
58+
5159
[Fact]
5260
public async Task Root_ReturnsOk()
5361
{

src/XtremeIdiots.Portal.Repository.Api.Tests.V1/Controllers/V1/HealthControllerTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public HealthControllerTests()
1919
}
2020

2121
[Fact]
22-
public async Task GetHealth_WhenHealthy_Returns200()
22+
public async Task GetReady_WhenHealthy_Returns200()
2323
{
2424
var healthReport = new HealthReport(
2525
entries: new Dictionary<string, HealthReportEntry>(),
@@ -30,14 +30,14 @@ public async Task GetHealth_WhenHealthy_Returns200()
3030
.Setup(x => x.CheckHealthAsync(It.IsAny<Func<HealthCheckRegistration, bool>?>(), It.IsAny<CancellationToken>()))
3131
.ReturnsAsync(healthReport);
3232

33-
var result = await _controller.GetHealth(CancellationToken.None);
33+
var result = await _controller.GetReady(CancellationToken.None);
3434

3535
var objectResult = Assert.IsType<ObjectResult>(result);
3636
Assert.Equal(StatusCodes.Status200OK, objectResult.StatusCode);
3737
}
3838

3939
[Fact]
40-
public async Task GetHealth_WhenUnhealthy_Returns503()
40+
public async Task GetReady_WhenUnhealthy_Returns503()
4141
{
4242
var healthReport = new HealthReport(
4343
entries: new Dictionary<string, HealthReportEntry>(),
@@ -48,7 +48,7 @@ public async Task GetHealth_WhenUnhealthy_Returns503()
4848
.Setup(x => x.CheckHealthAsync(It.IsAny<Func<HealthCheckRegistration, bool>?>(), It.IsAny<CancellationToken>()))
4949
.ReturnsAsync(healthReport);
5050

51-
var result = await _controller.GetHealth(CancellationToken.None);
51+
var result = await _controller.GetReady(CancellationToken.None);
5252

5353
var objectResult = Assert.IsType<ObjectResult>(result);
5454
Assert.Equal(StatusCodes.Status503ServiceUnavailable, objectResult.StatusCode);

src/XtremeIdiots.Portal.Repository.Api.Tests.V2/Controllers/V2/HealthControllerTests.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public HealthControllerTests()
1919
}
2020

2121
[Fact]
22-
public async Task GetHealth_WhenHealthy_Returns200()
22+
public async Task GetReady_WhenHealthy_Returns200()
2323
{
2424
// Arrange
2525
var healthReport = new HealthReport(
@@ -32,15 +32,15 @@ public async Task GetHealth_WhenHealthy_Returns200()
3232
.ReturnsAsync(healthReport);
3333

3434
// Act
35-
var result = await _controller.GetHealth(CancellationToken.None);
35+
var result = await _controller.GetReady(CancellationToken.None);
3636

3737
// Assert
3838
var objectResult = Assert.IsType<ObjectResult>(result);
3939
Assert.Equal(StatusCodes.Status200OK, objectResult.StatusCode);
4040
}
4141

4242
[Fact]
43-
public async Task GetHealth_WhenUnhealthy_Returns503()
43+
public async Task GetReady_WhenUnhealthy_Returns503()
4444
{
4545
// Arrange
4646
var healthReport = new HealthReport(
@@ -53,15 +53,15 @@ public async Task GetHealth_WhenUnhealthy_Returns503()
5353
.ReturnsAsync(healthReport);
5454

5555
// Act
56-
var result = await _controller.GetHealth(CancellationToken.None);
56+
var result = await _controller.GetReady(CancellationToken.None);
5757

5858
// Assert
5959
var objectResult = Assert.IsType<ObjectResult>(result);
6060
Assert.Equal(StatusCodes.Status503ServiceUnavailable, objectResult.StatusCode);
6161
}
6262

6363
[Fact]
64-
public async Task GetHealth_WhenDegraded_Returns503()
64+
public async Task GetReady_WhenDegraded_Returns503()
6565
{
6666
// Arrange
6767
var healthReport = new HealthReport(
@@ -74,15 +74,15 @@ public async Task GetHealth_WhenDegraded_Returns503()
7474
.ReturnsAsync(healthReport);
7575

7676
// Act
77-
var result = await _controller.GetHealth(CancellationToken.None);
77+
var result = await _controller.GetReady(CancellationToken.None);
7878

7979
// Assert
8080
var objectResult = Assert.IsType<ObjectResult>(result);
8181
Assert.Equal(StatusCodes.Status503ServiceUnavailable, objectResult.StatusCode);
8282
}
8383

8484
[Fact]
85-
public async Task GetHealth_WhenHealthy_ReturnsStatusInBody()
85+
public async Task GetReady_WhenHealthy_ReturnsStatusInBody()
8686
{
8787
// Arrange
8888
var entries = new Dictionary<string, HealthReportEntry>
@@ -106,7 +106,7 @@ public async Task GetHealth_WhenHealthy_ReturnsStatusInBody()
106106
.ReturnsAsync(healthReport);
107107

108108
// Act
109-
var result = await _controller.GetHealth(CancellationToken.None);
109+
var result = await _controller.GetReady(CancellationToken.None);
110110

111111
// Assert
112112
var objectResult = Assert.IsType<ObjectResult>(result);

src/XtremeIdiots.Portal.Repository.Api.V1/Controllers/V1/HealthController.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ public HealthController(HealthCheckService healthCheckService)
1818
_healthCheckService = healthCheckService;
1919
}
2020

21-
[HttpGet]
22-
public async Task<IActionResult> GetHealth(CancellationToken cancellationToken)
21+
[HttpGet("ready")]
22+
public async Task<IActionResult> GetReady(CancellationToken cancellationToken)
2323
{
2424
var result = await _healthCheckService.CheckHealthAsync(cancellationToken);
2525

@@ -38,4 +38,13 @@ public async Task<IActionResult> GetHealth(CancellationToken cancellationToken)
3838
})
3939
});
4040
}
41+
42+
[HttpGet("live")]
43+
public IActionResult GetLive()
44+
{
45+
return Ok(new
46+
{
47+
status = HealthStatus.Healthy.ToString(),
48+
});
49+
}
4150
}

src/XtremeIdiots.Portal.Repository.Api.V1/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using XtremeIdiots.Portal.Repository.Api.V1.OpenApi;
1616
using MX.Observability.ApplicationInsights.AspNetCore;
1717
using Scalar.AspNetCore;
18+
using Microsoft.Extensions.Diagnostics.HealthChecks;
1819

1920
var builder = WebApplication.CreateBuilder(args);
2021

@@ -118,6 +119,7 @@
118119
});
119120

120121
builder.Services.AddHealthChecks()
122+
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"])
121123
.AddCheck<XtremeIdiots.Portal.Repository.Api.V1.HealthChecks.SqlDatabaseHealthCheck>(
122124
name: "sql-database",
123125
tags: ["dependency"]);

src/XtremeIdiots.Portal.Repository.Api.V2/Controllers/V2/HealthController.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ public HealthController(HealthCheckService healthCheckService)
1818
_healthCheckService = healthCheckService;
1919
}
2020

21-
[HttpGet]
22-
public async Task<IActionResult> GetHealth(CancellationToken cancellationToken)
21+
[HttpGet("ready")]
22+
public async Task<IActionResult> GetReady(CancellationToken cancellationToken)
2323
{
2424
var result = await _healthCheckService.CheckHealthAsync(cancellationToken);
2525

@@ -38,4 +38,13 @@ public async Task<IActionResult> GetHealth(CancellationToken cancellationToken)
3838
})
3939
});
4040
}
41+
42+
[HttpGet("live")]
43+
public IActionResult GetLive()
44+
{
45+
return Ok(new
46+
{
47+
status = HealthStatus.Healthy.ToString(),
48+
});
49+
}
4150
}

src/XtremeIdiots.Portal.Repository.Api.V2/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using XtremeIdiots.Portal.Repository.Api.V2.OpenApi;
1414
using MX.Observability.ApplicationInsights.AspNetCore;
1515
using Scalar.AspNetCore;
16+
using Microsoft.Extensions.Diagnostics.HealthChecks;
1617

1718
var builder = WebApplication.CreateBuilder(args);
1819

@@ -112,6 +113,7 @@
112113
});
113114

114115
builder.Services.AddHealthChecks()
116+
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"])
115117
.AddCheck<XtremeIdiots.Portal.Repository.Api.V2.HealthChecks.SqlDatabaseHealthCheck>(
116118
name: "sql-database",
117119
tags: ["dependency"]);

0 commit comments

Comments
 (0)