Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ public void Valid_request_calculates_overall_status_from_checks()
{
var request = new ServerStatusRequest
{
ServerName = "TRANSACTION-SERVER-01",
Checks =
[
Check("PowerShell Runtime", "Passed"),
Expand All @@ -20,21 +19,20 @@ public void Valid_request_calculates_overall_status_from_checks()
};

request.OverallStatus = "Passed";
var result = ServerStatusValidator.Create("transaction-processor", request, DateTimeOffset.UtcNow);
var result = ServerStatusValidator.Create(" TRANSACTION-SERVER-01 ", request, DateTimeOffset.UtcNow);

Assert.Empty(result.Errors);
Assert.NotNull(result.Status);
Assert.Equal("Warning", result.Status!.OverallStatus);
Assert.Equal("transaction-processor", result.Status.ServerId);
Assert.Equal("TRANSACTION-SERVER-01", result.Status.ServerName);
}

[Fact]
public void Request_requires_server_name_and_at_least_one_check()
{
var result = ServerStatusValidator.Create(
"transaction-processor",
new ServerStatusRequest { ServerName = " ", Checks = [] },
" ",
new ServerStatusRequest { Checks = [] },
DateTimeOffset.UtcNow);

Assert.Contains(result.Errors, error => error.Contains("ServerName", StringComparison.Ordinal));
Expand All @@ -48,7 +46,6 @@ public void Request_rejects_unknown_check_status()
"transaction-processor",
new ServerStatusRequest
{
ServerName = "SERVER-01",
Checks = [Check("Disk Space", "Unknown")]
},
DateTimeOffset.UtcNow);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ private sealed class InMemoryRepository : IHealthMonitoringRepository
public Task<MonitoredService?> GetServiceAsync(string serviceId, CancellationToken cancellationToken) => Task.FromResult(SavedService);
public Task<MonitoredService> UpsertServiceAsync(MonitoredService service, CancellationToken cancellationToken) { SavedService = service; return Task.FromResult(service); }
public Task<IReadOnlyList<MonitoredService>> ListServicesAsync(bool includeArchived, CancellationToken cancellationToken) => throw new NotSupportedException();
public Task<ServerStatus?> GetServerStatusAsync(string serverId, CancellationToken cancellationToken) => throw new NotSupportedException();
public Task<ServerStatus?> GetServerStatusAsync(string serverName, CancellationToken cancellationToken) => throw new NotSupportedException();
public Task<ServerStatus> UpsertServerStatusAsync(ServerStatus status, CancellationToken cancellationToken) => throw new NotSupportedException();
public Task ArchiveServiceAsync(string serviceId, DateTimeOffset archivedAtUtc, CancellationToken cancellationToken) => throw new NotSupportedException();
public Task AddObservationAsync(HealthObservation observation, CancellationToken cancellationToken) => throw new NotSupportedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ public async Task Server_status_upsert_keeps_one_latest_row_per_server()
await using var database = await SqlServerTestDatabase.TryCreateAsync();
if (database is null) return;

var first = NewServerStatus("server-1", "SERVER-01", "Passed", "[]");
var second = NewServerStatus("server-1", "SERVER-01", "Warning", "[{\"name\":\"Disk Space\"}]");
var first = NewServerStatus(" SERVER-01 ", "Passed", "[]");
var second = NewServerStatus("SERVER-01", "Warning", "[{\"name\":\"Disk Space\"}]");

await database.Repository.UpsertServerStatusAsync(first, CancellationToken.None);
await database.Repository.UpsertServerStatusAsync(second, CancellationToken.None);

var current = await database.Repository.GetServerStatusAsync("server-1", CancellationToken.None);
var current = await database.Repository.GetServerStatusAsync("SERVER-01", CancellationToken.None);

Assert.NotNull(current);
Assert.Equal(first.Id, current.Id);
Expand Down Expand Up @@ -85,10 +85,9 @@ public async Task Server_status_upsert_keeps_one_latest_row_per_server()
RawResponseJson = raw
};

private static ServerStatus NewServerStatus(string serverId, string serverName, string overallStatus, string checksJson) => new()
private static ServerStatus NewServerStatus(string serverName, string overallStatus, string checksJson) => new()
{
Id = Guid.NewGuid(),
ServerId = serverId,
ServerName = serverName,
OverallStatus = overallStatus,
ChecksJson = checksJson,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ public void Maps_latest_server_status_and_preserves_structured_sub_checks()
{
var status = new ServerStatus
{
ServerId = "transaction-processor",
ServerName = "TRANSACTION-SERVER-01",
OverallStatus = "Warning",
UpdatedAtUtc = DateTimeOffset.Parse("2026-10-14T06:00:00Z"),
Expand Down
16 changes: 7 additions & 9 deletions HealthMonitoring/HealthMonitoring/Api/ServerStatusContracts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ namespace HealthMonitoring.Api;

public sealed class ServerStatusRequest
{
public string? ServerName { get; set; }
public string? OverallStatus { get; set; }
public List<SupportCheckResultRequest> Checks { get; set; } = [];
}
Expand All @@ -23,7 +22,6 @@ public sealed class SupportCheckResultRequest
}

public sealed record ServerStatusResponse(
string ServerId,
string ServerName,
string OverallStatus,
DateTimeOffset ReceivedAtUtc,
Expand All @@ -37,30 +35,30 @@ public static class ServerStatusValidator
private static readonly HashSet<string> AllowedStatuses = ["Passed", "Warning", "Failed"];
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);

public static ServerStatusValidationResult Create(string serverId, ServerStatusRequest request, DateTimeOffset now)
public static ServerStatusValidationResult Create(string serverName, ServerStatusRequest request, DateTimeOffset now)
{
var errors = ValidateRequest(serverId, request);
serverName = serverName?.Trim() ?? string.Empty;
var errors = ValidateRequest(serverName, request);

if (errors.Count > 0) return new ServerStatusValidationResult(errors, null);

var checks = request.Checks!;
return new ServerStatusValidationResult(errors, new ServerStatus
{
Id = Guid.NewGuid(),
ServerId = serverId,
ServerName = request.ServerName!.Trim(),
ServerName = serverName,
OverallStatus = CalculateOverallStatus(checks),
ChecksJson = JsonSerializer.Serialize(checks, JsonOptions),
ReceivedAtUtc = now,
UpdatedAtUtc = now
});
}

private static List<string> ValidateRequest(string serverId, ServerStatusRequest request)
private static List<string> ValidateRequest(string serverName, ServerStatusRequest request)
{
var errors = new List<string>();
if (string.IsNullOrWhiteSpace(serverId)) errors.Add("ServerId is required.");
if (string.IsNullOrWhiteSpace(request.ServerName)) errors.Add("ServerName is required.");
if (string.IsNullOrWhiteSpace(serverName)) errors.Add("ServerName is required.");
if (serverName.Length > 300) errors.Add("ServerName cannot exceed 300 characters.");
if (request.Checks is null || request.Checks.Count == 0) errors.Add("Checks must contain at least one check.");

foreach (var check in request.Checks ?? [])
Expand Down
15 changes: 6 additions & 9 deletions HealthMonitoring/HealthMonitoring/Api/ServerStatusEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,27 @@ public static class ServerStatusEndpoints

public static IEndpointRouteBuilder MapServerStatusEndpoints(this IEndpointRouteBuilder endpoints)
{
endpoints.MapPost("/api/services/{serverId}/support-checks", async (
string serverId,
endpoints.MapPost("/api/servers/{serverName}/support-checks", async (
string serverName,
ServerStatusRequest request,
IHealthMonitoringRepository repository,
CancellationToken cancellationToken) =>
{
var service = await repository.GetServiceAsync(serverId, cancellationToken);
if (service is null || service.ArchivedAtUtc is not null) return Results.NotFound();

var validation = ServerStatusValidator.Create(serverId, request, DateTimeOffset.UtcNow);
var validation = ServerStatusValidator.Create(serverName, request, DateTimeOffset.UtcNow);
if (validation.Errors.Count > 0)
return Results.ValidationProblem(validation.Errors
.GroupBy(error => error.Split('.')[0], StringComparer.OrdinalIgnoreCase)
.ToDictionary(group => group.Key, group => group.ToArray(), StringComparer.OrdinalIgnoreCase));

var existing = await repository.GetServerStatusAsync(serverId, cancellationToken);
var existing = await repository.GetServerStatusAsync(validation.Status!.ServerName, cancellationToken);
var status = validation.Status!;
status.Id = existing?.Id ?? status.Id;
var saved = await repository.UpsertServerStatusAsync(status, cancellationToken);
var checks = JsonSerializer.Deserialize<List<SupportCheckResultRequest>>(saved.ChecksJson, JsonOptions) ?? [];
var response = new ServerStatusResponse(saved.ServerId, saved.ServerName, saved.OverallStatus, saved.ReceivedAtUtc, saved.UpdatedAtUtc, checks);
var response = new ServerStatusResponse(saved.ServerName, saved.OverallStatus, saved.ReceivedAtUtc, saved.UpdatedAtUtc, checks);

return existing is null
? Results.Created($"/api/services/{saved.ServerId}/support-checks", response)
? Results.Created($"/api/servers/{Uri.EscapeDataString(saved.ServerName)}/support-checks", response)
: Results.Ok(response);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ else if (_model is null)
else
{
<section class="server-check-summary">
<div><p class="eyebrow">SERVER</p><h2>@_model.ServerName</h2><p class="muted">@_model.ServerId</p></div>
<div><p class="eyebrow">SERVER</p><h2>@_model.ServerName</h2></div>
<div class="server-check-summary-status"><ServerCheckStatusBadge Status="@_model.OverallStatus" /><span>Updated @_model.UpdatedAtUtc.ToLocalTime().ToString("dd MMM yyyy, HH:mm:ss")</span></div>
</section>
<section class="panel server-check-panel">
Expand Down
1 change: 0 additions & 1 deletion HealthMonitoring/HealthMonitoring/Domain/ServerStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ namespace HealthMonitoring.Domain;
public sealed class ServerStatus
{
public Guid Id { get; set; }
public string ServerId { get; set; } = string.Empty;
public string ServerName { get; set; } = string.Empty;
public string OverallStatus { get; set; } = string.Empty;
public string ChecksJson { get; set; } = "[]";
Expand Down
Loading
Loading