85 lines
3.4 KiB
C#
85 lines
3.4 KiB
C#
using ClaudeDo.Data;
|
|
using ClaudeDo.Worker.Config;
|
|
using ClaudeDo.Worker.Git;
|
|
using ClaudeDo.Worker.Hub;
|
|
using ClaudeDo.Worker.Planning;
|
|
using ClaudeDo.Worker.Queue;
|
|
using ClaudeDo.Worker.State;
|
|
using ClaudeDo.Worker.Tickets;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
|
|
namespace ClaudeDo.Worker.Tests.Infrastructure;
|
|
|
|
/// Test-only helper that wires TaskStateService and PlanningChainCoordinator
|
|
/// against a shared DB factory, breaking the Func cycle between them.
|
|
public static class TaskStateServiceBuilder
|
|
{
|
|
public sealed record Built(
|
|
TaskStateService State,
|
|
PlanningChainCoordinator Chain,
|
|
CapturingHubContext Hub,
|
|
Func<int> WakeCount,
|
|
CountingQueueWaker Waker,
|
|
RunCancellationRegistry RunCancels);
|
|
|
|
public static Built Build(
|
|
IDbContextFactory<ClaudeDoDbContext> dbFactory,
|
|
Func<IActiveMergeState>? mergeState = null,
|
|
BaseDirtyChecker? baseDirtyChecker = null)
|
|
{
|
|
var hub = new CapturingHubContext();
|
|
var broadcaster = new HubBroadcaster(hub);
|
|
var waker = new CountingQueueWaker();
|
|
var runCancels = new RunCancellationRegistry(NullLogger<RunCancellationRegistry>.Instance);
|
|
|
|
// Unconfigured on purpose: no base URL, no PAT file at this (unused) path — IsConfigured
|
|
// is false, so TicketStatusSync.SyncAsync short-circuits before touching the DB or the
|
|
// network. Tests that care about ticket sync build TicketStatusSync themselves.
|
|
var ticketConfig = new TicketSystemConfig(
|
|
new WorkerConfig(),
|
|
new TicketPatStore(new DpapiTokenStore(Path.Combine(Path.GetTempPath(), $"cdo-unused-pat-{Guid.NewGuid():N}"))));
|
|
var ticketSync = new TicketStatusSync(
|
|
ticketConfig,
|
|
new TicketClientFactory(ticketConfig, new NoopHttpClientFactory(), NullLogger<BandelTicketClient>.Instance),
|
|
dbFactory,
|
|
NullLogger<TicketStatusSync>.Instance);
|
|
|
|
TaskStateService? state = null;
|
|
var chain = new PlanningChainCoordinator(dbFactory, () => state!);
|
|
state = new TaskStateService(
|
|
dbFactory,
|
|
broadcaster,
|
|
waker,
|
|
chain,
|
|
runCancels,
|
|
mergeState ?? (() => NoActiveMergeState.Instance),
|
|
baseDirtyChecker ?? new BaseDirtyChecker(new ClaudeDo.Data.Git.GitService(), NullLogger<BaseDirtyChecker>.Instance),
|
|
NullLogger<TaskStateService>.Instance,
|
|
ticketSync);
|
|
|
|
return new Built(state, chain, hub, () => waker.Count, waker, runCancels);
|
|
}
|
|
}
|
|
|
|
file sealed class NoActiveMergeState : IActiveMergeState
|
|
{
|
|
public static readonly NoActiveMergeState Instance = new();
|
|
public bool HasActiveMerge(string taskId) => false;
|
|
}
|
|
|
|
// Never actually invoked: TicketSystemConfig.IsConfigured is false in every TaskStateServiceBuilder
|
|
// test, so TicketClientFactory.Create() returns null before this factory would be called.
|
|
file sealed class NoopHttpClientFactory : IHttpClientFactory
|
|
{
|
|
public HttpClient CreateClient(string name) => throw new InvalidOperationException(
|
|
"Unexpected: ticket sync is not configured in tests built via TaskStateServiceBuilder.");
|
|
}
|
|
|
|
public sealed class CountingQueueWaker : IQueueWaker
|
|
{
|
|
private int _count;
|
|
public int Count => Volatile.Read(ref _count);
|
|
public void Wake() => Interlocked.Increment(ref _count);
|
|
}
|