From b44954545b901e3fd7a88c0b974d406ae3705e50 Mon Sep 17 00:00:00 2001 From: voltsrage Date: Wed, 5 Aug 2026 19:22:54 +0800 Subject: [PATCH] Fix issues with Gitea using workers --- .gitea/workflows/ci.yml | 9 +++++++- .../Fixtures/GatewayApiFixture.cs | 10 +++++++-- .../LocalEscalationWorkerService.cs | 22 +++++++++++++++++++ .../LocalPagingWorkerService.cs | 22 +++++++++++++++++++ .../Fixtures/ApiFixture.cs | 10 +++++++++ docker-compose.yml | 5 +++++ infra/rabbitmq/rabbitmq.conf | 4 ++++ 7 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 infra/rabbitmq/rabbitmq.conf diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index ddfdd66..587a999 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -86,18 +86,25 @@ jobs: - name: Test env: - # Defaults match docker-compose.yml published ports; fixtures fall back to these anyway. + # act_runner runs in its own container; Compose publishes ports on the VM. + # host.docker.internal reaches those published ports (requires extra_hosts + # host-gateway on the runner). RabbitMQ guest remote access is enabled in + # infra/rabbitmq/rabbitmq.conf for this topology. ConnectionStrings__DefaultConnection: "Host=host.docker.internal;Port=5436;Database=vigilcare_test;Username=postgres;Password=password" ConnectionStrings__GatewayDb: "Host=host.docker.internal;Port=5437;Database=vigilcare_ward_test;Username=postgres;Password=password" Redis__ConnectionString: "host.docker.internal:6382,defaultDatabase=1,allowAdmin=true" Gateway__Redis__ConnectionString: "host.docker.internal:6383,defaultDatabase=2,allowAdmin=true" RabbitMq__Host: "host.docker.internal" RabbitMq__Port: "5674" + Gateway__RabbitMq__Host: "host.docker.internal" Gateway__RabbitMq__Port: "5675" Kafka__BootstrapServers: "host.docker.internal:9092" Kafka__ReplicationFactor: "1" + Kafka__SecurityProtocol: "Plaintext" Elasticsearch__Uri: "http://host.docker.internal:9200" Minio__Endpoint: "host.docker.internal:9005" + Minio__UseSSL: "false" + ASPNETCORE_ENVIRONMENT: "Testing" run: | dotnet test VigilCareClinical.sln -c Release --no-build \ --logger "trx;LogFileName=test-results.trx" \ diff --git a/VigilCare.WardGateway.Tests/Fixtures/GatewayApiFixture.cs b/VigilCare.WardGateway.Tests/Fixtures/GatewayApiFixture.cs index be602a3..f8325ca 100644 --- a/VigilCare.WardGateway.Tests/Fixtures/GatewayApiFixture.cs +++ b/VigilCare.WardGateway.Tests/Fixtures/GatewayApiFixture.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using StackExchange.Redis; public class GatewayApiFixture : WebApplicationFactory, IAsyncLifetime @@ -13,9 +14,9 @@ public class GatewayApiFixture : WebApplicationFactory, IAsyncLifetime Environment.GetEnvironmentVariable("ConnectionStrings__GatewayDb") ?? "Host=localhost;Port=5437;Database=vigilcare_ward_test;Username=postgres;Password=password"; + // Never fall back to the API Redis__* env — that points at a different instance/DB index. public static string RedisConnection { get; } = Environment.GetEnvironmentVariable("Gateway__Redis__ConnectionString") - ?? Environment.GetEnvironmentVariable("Redis__ConnectionString") ?? "localhost:6383,defaultDatabase=2,allowAdmin=true"; protected override void ConfigureWebHost(IWebHostBuilder builder) @@ -30,12 +31,14 @@ public class GatewayApiFixture : WebApplicationFactory, IAsyncLifetime ["RabbitMq:Host"] = Environment.GetEnvironmentVariable("Gateway__RabbitMq__Host") ?? Environment.GetEnvironmentVariable("RabbitMq__Host") ?? "localhost", + // Do not fall back to RabbitMq__Port (API uses 5674); gateway broker is 5675. ["RabbitMq:Port"] = Environment.GetEnvironmentVariable("Gateway__RabbitMq__Port") - ?? Environment.GetEnvironmentVariable("RabbitMq__Port") ?? "5675", ["RabbitMq:Username"] = Environment.GetEnvironmentVariable("RabbitMq__Username") ?? "guest", ["RabbitMq:Password"] = Environment.GetEnvironmentVariable("RabbitMq__Password") ?? "guest", ["RabbitMq:PagingAckTimeoutMs"] = "5000", + // Neutralize appsettings.Production.json if the process env was Production. + ["RabbitMq:UseSsl"] = "false", ["CentralApi:BaseUrl"] = "http://127.0.0.1:1", ["Gateway:GatewayId"] = "22222222-2222-2222-2222-222222222222", ["Gateway:SiteId"] = "11111111-1111-1111-1111-111111111111", @@ -54,6 +57,9 @@ public class GatewayApiFixture : WebApplicationFactory, IAsyncLifetime builder.ConfigureServices(services => { + services.Configure(o => + o.BackgroundServiceExceptionBehavior = BackgroundServiceExceptionBehavior.Ignore); + services.AddAuthentication(options => { options.DefaultAuthenticateScheme = TestingAuthHandler.SchemeName; diff --git a/VigilCare.WardGateway/BackgroundService/LocalEscalationWorkerService.cs b/VigilCare.WardGateway/BackgroundService/LocalEscalationWorkerService.cs index 4da453d..f00df8e 100644 --- a/VigilCare.WardGateway/BackgroundService/LocalEscalationWorkerService.cs +++ b/VigilCare.WardGateway/BackgroundService/LocalEscalationWorkerService.cs @@ -24,6 +24,28 @@ public sealed class LocalEscalationWorkerService : BackgroundService { await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); + while (!stoppingToken.IsCancellationRequested) + { + try + { + await RunConsumerAsync(stoppingToken); + return; + } + catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) + { + return; + } + catch (Exception ex) + { + _logger.LogError(ex, + "LocalEscalationWorkerService disconnected — retrying in 5s"); + await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); + } + } + } + + private async Task RunConsumerAsync(CancellationToken stoppingToken) + { var o = _opts.Value; var factory = RabbitMqConnectionFactory.Create(o, dispatchConsumersAsync: true); diff --git a/VigilCare.WardGateway/BackgroundService/LocalPagingWorkerService.cs b/VigilCare.WardGateway/BackgroundService/LocalPagingWorkerService.cs index 584822c..ba68d62 100644 --- a/VigilCare.WardGateway/BackgroundService/LocalPagingWorkerService.cs +++ b/VigilCare.WardGateway/BackgroundService/LocalPagingWorkerService.cs @@ -25,6 +25,28 @@ public sealed class LocalPagingWorkerService : BackgroundService { await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); + while (!stoppingToken.IsCancellationRequested) + { + try + { + await RunConsumerAsync(stoppingToken); + return; + } + catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) + { + return; + } + catch (Exception ex) + { + _logger.LogError(ex, + "LocalPagingWorkerService disconnected — retrying in 5s"); + await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); + } + } + } + + private async Task RunConsumerAsync(CancellationToken stoppingToken) + { var o = _opts.Value; var factory = RabbitMqConnectionFactory.Create(o, dispatchConsumersAsync: true); diff --git a/VigilCareClinicalAPI.Tests/Fixtures/ApiFixture.cs b/VigilCareClinicalAPI.Tests/Fixtures/ApiFixture.cs index 595fd43..5a981ab 100644 --- a/VigilCareClinicalAPI.Tests/Fixtures/ApiFixture.cs +++ b/VigilCareClinicalAPI.Tests/Fixtures/ApiFixture.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using StackExchange.Redis; public class ApiFixture : WebApplicationFactory, IAsyncLifetime @@ -40,16 +41,22 @@ public class ApiFixture : WebApplicationFactory, IAsyncLifetime ["RabbitMq:Password"] = Environment.GetEnvironmentVariable("RabbitMq__Password") ?? "guest", ["RabbitMq:PagingAckTimeoutMs"] = "5000", ["RabbitMq:VirtualHost"] = "vigilcare_test", + // Neutralize appsettings.Production.json if the process env was Production. + ["RabbitMq:UseSsl"] = "false", ["Kafka:BootstrapServers"] = Environment.GetEnvironmentVariable("Kafka__BootstrapServers") ?? "localhost:9092", ["Kafka:ReplicationFactor"] = Environment.GetEnvironmentVariable("Kafka__ReplicationFactor") ?? "1", + ["Kafka:SecurityProtocol"] = + Environment.GetEnvironmentVariable("Kafka__SecurityProtocol") ?? "Plaintext", ["Kafka:NotificationPublisherGroupId"] = "notification-publisher-integration-test", ["Kafka:NotificationPublisherAutoOffsetReset"] = "Latest", ["Elasticsearch:Uri"] = Environment.GetEnvironmentVariable("Elasticsearch__Uri") ?? "http://localhost:9200", ["Minio:Endpoint"] = Environment.GetEnvironmentVariable("Minio__Endpoint") ?? "localhost:9005", + ["Minio:UseSSL"] = + Environment.GetEnvironmentVariable("Minio__UseSSL") ?? "false", ["Fhir:ApiKey"] = "dev-integration-key-change-in-production", ["ApiKey:Gateway"] = GatewayAuthHelper.DevGatewayKey, }); @@ -59,6 +66,9 @@ public class ApiFixture : WebApplicationFactory, IAsyncLifetime builder.ConfigureServices(services => { + services.Configure(o => + o.BackgroundServiceExceptionBehavior = BackgroundServiceExceptionBehavior.Ignore); + services.AddAuthentication(options => { options.DefaultAuthenticateScheme = TestingAuthHandler.SchemeName; diff --git a/docker-compose.yml b/docker-compose.yml index f6fda86..7f11e8b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -88,6 +88,9 @@ services: environment: RABBITMQ_DEFAULT_USER: guest RABBITMQ_DEFAULT_PASS: guest + volumes: + # Allow guest from host.docker.internal / CI runner (dev only). + - ./infra/rabbitmq/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro healthcheck: test: ["CMD", "rabbitmq-diagnostics", "ping"] interval: 10s @@ -181,6 +184,8 @@ services: environment: RABBITMQ_DEFAULT_USER: guest RABBITMQ_DEFAULT_PASS: guest + volumes: + - ./infra/rabbitmq/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro healthcheck: test: ["CMD", "rabbitmq-diagnostics", "ping"] interval: 10s diff --git a/infra/rabbitmq/rabbitmq.conf b/infra/rabbitmq/rabbitmq.conf new file mode 100644 index 0000000..5efa797 --- /dev/null +++ b/infra/rabbitmq/rabbitmq.conf @@ -0,0 +1,4 @@ +## Dev/CI only — allow the default guest user from non-loopback clients. +## Needed when tests run inside act_runner and reach brokers via host.docker.internal. +## Do not use this setting on production brokers. +loopback_users.guest = false