Skip to content

Commit 2393161

Browse files
dcl10claude
andauthored
Debug/queues not existing at startup (#55)
* Fix queue name mismatch between API and LLM worker API was sending to "project-description" while the worker polled "project-descriptions", so messages were never delivered. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Auto-create Azure Storage queues on startup Azurite does not provision queues automatically. Both the API and the LLM worker now call CreateIfNotExistsAsync at startup so the queues are always ready without manual setup. The call is idempotent, so it is safe against real Azure Storage in production too. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix integration tests failing in CI due to QueueInitializerService QueueInitializerService was registered as a hosted service that tries to connect to real Azure Storage on startup, but ApiFactory only mocked the IMessageChannel singletons — not the hosted service itself. In CI (no Azurite), this caused all integration tests to fail at host startup. Added InternalsVisibleTo for the test assembly so ApiFactory can remove QueueInitializerService from the DI container by type before the test host starts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 63af90e commit 2393161

6 files changed

Lines changed: 40 additions & 9 deletions

File tree

backend/src/SkillMatrixLlm.Api/Config/MessageQueueOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public record MessageQueueOptions
77
public string ConnectionString { get; init; } = string.Empty;
88

99
/// <summary>Queue name for outbound <c>ProjectDescriptionPayload</c> messages.</summary>
10-
public string ProjectDescriptionQueueName { get; init; } = "project-description";
10+
public string ProjectDescriptionQueueName { get; init; } = "project-descriptions";
1111

1212
/// <summary>Queue name for inbound <c>SkillRequirementsResult</c> messages from the LLM service.</summary>
1313
public string SkillRequirementsQueueName { get; init; } = "skill-requirements";

backend/src/SkillMatrixLlm.Api/Extensions/ServiceCollectionExtensions.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,23 @@ public static IServiceCollection AddEmailSender(this IServiceCollection s, IConf
4545
}
4646

4747
/// <summary>
48-
/// Registers <see cref="IMessageChannel{T}"/> implementations backed by Azure Queue Storage.
48+
/// Registers <see cref="IMessageChannel{T}"/> implementations backed by Azure Queue Storage
49+
/// and a hosted service that creates the queues on startup if they do not already exist.
4950
/// </summary>
5051
public static IServiceCollection AddMessageQueues(this IServiceCollection s, IConfiguration c)
5152
{
5253
var options = c.GetSection("MessageQueue").Get<MessageQueueOptions>() ?? new MessageQueueOptions();
5354

54-
// Factory delegates defer QueueClient construction until first resolve,
55-
// which allows test hosts to override these registrations without triggering
56-
// the QueueClient constructor (which requires a valid connection string).
55+
var projectDescClient = CreateQueueClient(options.ConnectionString, options.ProjectDescriptionQueueName);
56+
var skillReqClient = CreateQueueClient(options.ConnectionString, options.SkillRequirementsQueueName);
57+
5758
s.AddSingleton<IMessageChannel<ProjectDescriptionPayload>>(_ =>
58-
new AzureStorageQueueMessageChannel<ProjectDescriptionPayload>(
59-
CreateQueueClient(options.ConnectionString, options.ProjectDescriptionQueueName)));
59+
new AzureStorageQueueMessageChannel<ProjectDescriptionPayload>(projectDescClient));
6060

6161
s.AddSingleton<IMessageChannel<SkillRequirementsResult>>(_ =>
62-
new AzureStorageQueueMessageChannel<SkillRequirementsResult>(
63-
CreateQueueClient(options.ConnectionString, options.SkillRequirementsQueueName)));
62+
new AzureStorageQueueMessageChannel<SkillRequirementsResult>(skillReqClient));
63+
64+
s.AddHostedService(_ => new QueueInitializerService([projectDescClient, skillReqClient]));
6465

6566
return s;
6667
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace SkillMatrixLlm.Api.Services;
2+
3+
using Azure.Storage.Queues;
4+
5+
/// <summary>Creates Azure Storage Queues on startup if they do not already exist.</summary>
6+
internal sealed class QueueInitializerService(QueueClient[] clients) : IHostedService
7+
{
8+
/// <inheritdoc/>
9+
public async Task StartAsync(CancellationToken cancellationToken)
10+
{
11+
foreach (var client in clients)
12+
await client.CreateIfNotExistsAsync(cancellationToken: cancellationToken);
13+
}
14+
15+
/// <inheritdoc/>
16+
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
17+
}

backend/src/SkillMatrixLlm.Api/SkillMatrixLlm.Api.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
<UserSecretsId>6a7c2c10-7306-41a6-8b41-d1490d650476</UserSecretsId>
1111
</PropertyGroup>
1212

13+
<ItemGroup>
14+
<InternalsVisibleTo Include="SkillMatrixLlm.Api.IntegrationTests" />
15+
</ItemGroup>
16+
1317
<ItemGroup>
1418
<!-- JWT Bearer authentication — validates Keycloak-issued tokens -->
1519
<PackageReference Include="Azure.Storage.Queues" Version="12.25.0" />

backend/src/SkillMatrixLlm.LlmWorker/Worker.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ public partial class Worker(
3737
/// <inheritdoc />
3838
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
3939
{
40+
await inputQueue.CreateIfNotExistsAsync(cancellationToken: stoppingToken);
41+
await outputQueue.CreateIfNotExistsAsync(cancellationToken: stoppingToken);
42+
await poisonQueue.CreateIfNotExistsAsync(cancellationToken: stoppingToken);
43+
4044
var opts = workerOptions.Value;
4145

4246
while (!stoppingToken.IsCancellationRequested)

backend/tests/SkillMatrixLlm.Api.IntegrationTests/Fixtures/ApiFactory.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ namespace SkillMatrixLlm.Api.Tests.Fixtures;
1414
using Messaging;
1515
using Models.Recommendations;
1616
using Moq;
17+
using Services;
1718
using Services.Contracts;
1819

1920
/// <summary>
@@ -40,6 +41,10 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) =>
4041
services.AddDbContext<AppDbContext>(o =>
4142
o.UseInMemoryDatabase("TestDb").UseInternalServiceProvider(inMemoryProvider));
4243

44+
var queueInitDescriptor = services.SingleOrDefault(d => d.ImplementationType == typeof(QueueInitializerService));
45+
if (queueInitDescriptor is not null)
46+
services.Remove(queueInitDescriptor);
47+
4348
services.AddTransient(_ => Mock.Of<IEmailSender>());
4449
services.AddSingleton(_ => Mock.Of<IKeycloakDataSeeder>());
4550
services.AddSingleton(_ => Mock.Of<IMessageChannel<ProjectDescriptionPayload>>());

0 commit comments

Comments
 (0)