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 @@ -28,9 +28,9 @@ public sealed record ServiceRegistrationResult(string ServiceId, string Action,

public interface IHealthMonitoringRegistrationClient
{
Task<ServiceRegistrationResult> RegisterAsync(ServiceRegistrationOptions options, CancellationToken cancellationToken = default);
Task<ServiceRegistrationResult?> RegisterAsync(ServiceRegistrationOptions options, CancellationToken cancellationToken = default);
Task SetDependencyMappingsAsync(string serviceId, IEnumerable<DependencyMappingOptions> mappings, CancellationToken cancellationToken = default);
Task<ServiceRegistrationResult> RegisterAndConfigureAsync(ServiceRegistrationOptions options, IEnumerable<DependencyMappingOptions> mappings, CancellationToken cancellationToken = default);
Task<ServiceRegistrationResult?> RegisterAndConfigureAsync(ServiceRegistrationOptions options, IEnumerable<DependencyMappingOptions> mappings, CancellationToken cancellationToken = default);
}

public sealed class HealthMonitoringRegistrationClient(
Expand All @@ -39,7 +39,7 @@ public sealed class HealthMonitoringRegistrationClient(
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);

public async Task<ServiceRegistrationResult> RegisterAsync(ServiceRegistrationOptions options, CancellationToken cancellationToken = default)
public async Task<ServiceRegistrationResult?> RegisterAsync(ServiceRegistrationOptions options, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(options);
var monitorType = ParseMonitorType(options.MonitorType);
Expand Down Expand Up @@ -71,12 +71,20 @@ public async Task<ServiceRegistrationResult> RegisterAsync(ServiceRegistrationOp
"POST /api/services/register body: {RequestBody}",
JsonSerializer.Serialize(loggedPayload, JsonOptions));

using var response = await httpClient.PostAsJsonAsync("api/services/register", payload, JsonOptions, cancellationToken);
try
{
using var response = await httpClient.PostAsJsonAsync("api/services/register", payload, JsonOptions, cancellationToken);

await EnsureSuccessAsync(response, "register service", cancellationToken);
var result = await response.Content.ReadFromJsonAsync<ServiceRegistrationResponsePayload>(JsonOptions, cancellationToken)
?? throw new InvalidOperationException("The monitoring server returned an empty registration response.");
return new ServiceRegistrationResult(result.ServiceId, result.Action, result.Service.Id);
await EnsureSuccessAsync(response, "register service", cancellationToken);
var result = await response.Content.ReadFromJsonAsync<ServiceRegistrationResponsePayload>(JsonOptions, cancellationToken)
?? throw new InvalidOperationException("The monitoring server returned an empty registration response.");
return new ServiceRegistrationResult(result.ServiceId, result.Action, result.Service.Id);
}
catch (Exception exception) when (!cancellationToken.IsCancellationRequested)
{
logger?.LogWarning(exception, "Unable to register service '{ServiceId}' with health monitoring.", options.ServiceId);
return null;
}
}

public async Task SetDependencyMappingsAsync(string serviceId, IEnumerable<DependencyMappingOptions> mappings, CancellationToken cancellationToken = default)
Expand All @@ -100,9 +108,10 @@ public async Task SetDependencyMappingsAsync(string serviceId, IEnumerable<Depen
}
}

public async Task<ServiceRegistrationResult> RegisterAndConfigureAsync(ServiceRegistrationOptions options, IEnumerable<DependencyMappingOptions> mappings, CancellationToken cancellationToken = default)
public async Task<ServiceRegistrationResult?> RegisterAndConfigureAsync(ServiceRegistrationOptions options, IEnumerable<DependencyMappingOptions> mappings, CancellationToken cancellationToken = default)
{
var result = await RegisterAsync(options, cancellationToken);
if (result is null) return null;
await SetDependencyMappingsAsync(options.ServiceId, mappings, cancellationToken);
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public async Task RegisterAsync_posts_service_configuration_and_returns_acknowle
Version = "1.2.3"
});

Assert.NotNull(result);
Assert.Equal("created", result.Action);
Assert.Equal(Guid.Parse("11111111-1111-1111-1111-111111111111"), result.MonitoredServiceId);
Assert.Equal(HttpMethod.Post, handler.Request!.Method);
Expand Down Expand Up @@ -81,6 +82,29 @@ await client.RegisterAsync(new ServiceRegistrationOptions
Assert.DoesNotContain("super-secret", logger.Messages.Single());
}

[Fact]
public async Task RegisterAsync_returns_null_and_logs_when_registration_call_fails()
{
var handler = new RecordingHandler(_ => new HttpResponseMessage(HttpStatusCode.ServiceUnavailable)
{
Content = new StringContent("monitoring unavailable")
});
var logger = new RecordingLogger<HealthMonitoringRegistrationClient>();
var client = new HealthMonitoringRegistrationClient(
new HttpClient(handler) { BaseAddress = new Uri(MonitoringBaseUrl) },
logger);

var result = await client.RegisterAsync(new ServiceRegistrationOptions
{
ServiceId = PaymentsApiServiceId,
Name = "Payments API",
HealthUrl = new Uri("https://payments-api/health")
});

Assert.Null(result);
Assert.Contains(logger.Messages, message => message.Contains("register service", StringComparison.OrdinalIgnoreCase));
}

[Fact]
public async Task SetDependencyMappingsAsync_resolves_target_service_and_upserts_mapping()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ private sealed class RecordingRegistrationClient : IHealthMonitoringRegistration
public ServiceRegistrationOptions? Service { get; private set; }
public IEnumerable<DependencyMappingOptions>? Dependencies { get; private set; }

public Task<ServiceRegistrationResult> RegisterAsync(ServiceRegistrationOptions options, CancellationToken cancellationToken = default)
public Task<ServiceRegistrationResult?> RegisterAsync(ServiceRegistrationOptions options, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
throw new NotSupportedException($"RegisterAsync is not used for {options.ServiceId}.");
Expand All @@ -94,12 +94,12 @@ public Task SetDependencyMappingsAsync(string serviceId, IEnumerable<DependencyM
throw new NotSupportedException($"SetDependencyMappingsAsync is not used for {serviceId} with {mappings.Count()} mappings.");
}

public Task<ServiceRegistrationResult> RegisterAndConfigureAsync(ServiceRegistrationOptions options, IEnumerable<DependencyMappingOptions> mappings, CancellationToken cancellationToken = default)
public Task<ServiceRegistrationResult?> RegisterAndConfigureAsync(ServiceRegistrationOptions options, IEnumerable<DependencyMappingOptions> mappings, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
Service = options;
Dependencies = mappings;
return Task.FromResult(new ServiceRegistrationResult(options.ServiceId, "created", Guid.NewGuid()));
return Task.FromResult<ServiceRegistrationResult?>(new ServiceRegistrationResult(options.ServiceId, "created", Guid.NewGuid()));
}
}
}
Loading