fix(worker): broadcast TaskUpdated after online-inbox import

This commit is contained in:
mika kuns
2026-08-07 09:46:43 +02:00
parent a7a3545e2b
commit 3acb1cba8f
2 changed files with 32 additions and 3 deletions
@@ -1,6 +1,7 @@
using ClaudeDo.Data; using ClaudeDo.Data;
using ClaudeDo.Data.Models; using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories; using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Hub;
using ClaudeDo.Worker.Online.Interfaces; using ClaudeDo.Worker.Online.Interfaces;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
@@ -15,19 +16,22 @@ public sealed class OnlineSyncService : BackgroundService
private readonly IOnlineAuthProvider _auth; private readonly IOnlineAuthProvider _auth;
private readonly OnlineInboxConfig _config; private readonly OnlineInboxConfig _config;
private readonly ILogger<OnlineSyncService> _logger; private readonly ILogger<OnlineSyncService> _logger;
private readonly HubBroadcaster _broadcaster;
public OnlineSyncService( public OnlineSyncService(
IDbContextFactory<ClaudeDoDbContext> dbFactory, IDbContextFactory<ClaudeDoDbContext> dbFactory,
IOnlineInboxApi api, IOnlineInboxApi api,
IOnlineAuthProvider auth, IOnlineAuthProvider auth,
OnlineInboxConfig config, OnlineInboxConfig config,
ILogger<OnlineSyncService> logger) ILogger<OnlineSyncService> logger,
HubBroadcaster broadcaster)
{ {
_dbFactory = dbFactory; _dbFactory = dbFactory;
_api = api; _api = api;
_auth = auth; _auth = auth;
_config = config; _config = config;
_logger = logger; _logger = logger;
_broadcaster = broadcaster;
} }
protected override async Task ExecuteAsync(CancellationToken stoppingToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
@@ -129,6 +133,8 @@ public sealed class OnlineSyncService : BackgroundService
CommitType = CommitTypeRegistry.DefaultType, CommitType = CommitTypeRegistry.DefaultType,
}; };
await tasks.AddAsync(entity, ct); await tasks.AddAsync(entity, ct);
// Without this the imported task only shows up after a manual reload.
await _broadcaster.TaskUpdated(entity.Id);
await _api.MarkImportedAsync(remote.Id, ct); await _api.MarkImportedAsync(remote.Id, ct);
_logger.LogInformation("OnlineSyncService: imported task {Id} ('{Title}')", remote.Id, remote.Title); _logger.LogInformation("OnlineSyncService: imported task {Id} ('{Title}')", remote.Id, remote.Title);
@@ -1,6 +1,7 @@
using ClaudeDo.Data; using ClaudeDo.Data;
using ClaudeDo.Data.Models; using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories; using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Hub;
using ClaudeDo.Worker.Online; using ClaudeDo.Worker.Online;
using ClaudeDo.Worker.Tests.Infrastructure; using ClaudeDo.Worker.Tests.Infrastructure;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
@@ -55,7 +56,8 @@ public sealed class OnlineSyncServiceTests : IDisposable
} }
} }
private OnlineSyncService BuildService(FakeApi api, string? token = "test-token", bool enabled = true) private OnlineSyncService BuildService(
FakeApi api, string? token = "test-token", bool enabled = true, HubBroadcaster? broadcaster = null)
{ {
var config = new OnlineInboxConfig { Enabled = enabled, PollIntervalSeconds = 60 }; var config = new OnlineInboxConfig { Enabled = enabled, PollIntervalSeconds = 60 };
var auth = new StaticTokenAuthProvider(token); var auth = new StaticTokenAuthProvider(token);
@@ -64,7 +66,8 @@ public sealed class OnlineSyncServiceTests : IDisposable
api, api,
auth, auth,
config, config,
NullLogger<OnlineSyncService>.Instance); NullLogger<OnlineSyncService>.Instance,
broadcaster ?? new HubBroadcaster(new CapturingHubContext()));
} }
private async Task<(string ListId, ClaudeDoDbContext Ctx, TaskRepository Tasks, ListRepository Lists)> SeedAsync() private async Task<(string ListId, ClaudeDoDbContext Ctx, TaskRepository Tasks, ListRepository Lists)> SeedAsync()
@@ -103,6 +106,26 @@ public sealed class OnlineSyncServiceTests : IDisposable
Assert.Contains(remoteId, api.MarkedImported); Assert.Contains(remoteId, api.MarkedImported);
} }
[Fact]
public async Task Tick_Imports_RemoteTask_BroadcastsTaskUpdated()
{
var (listId, ctx, _, _) = await SeedAsync();
using var _ = ctx;
var remoteId = Guid.NewGuid().ToString();
var api = new FakeApi
{
UnimportedTasks = [new RemoteTask(remoteId, listId, "From Web", "desc", DateTimeOffset.UtcNow)],
};
var hubContext = new CapturingHubContext();
var svc = BuildService(api, broadcaster: new HubBroadcaster(hubContext));
await svc.TickAsync(CancellationToken.None);
Assert.Contains(hubContext.Proxy.Calls,
c => c.Method == "TaskUpdated" && (string)c.Args[0]! == remoteId);
}
[Fact] [Fact]
public async Task Tick_UnknownList_Skips_And_DoesNotMark() public async Task Tick_UnknownList_Skips_And_DoesNotMark()
{ {