using ClaudeDo.Worker; using ClaudeDo.Worker.Config; using ClaudeDo.Worker.External; using ClaudeDo.Worker.Hub; using ClaudeDo.Worker.Tests.Infrastructure; using ClaudeDo.Worker.Tickets; using Microsoft.Extensions.Logging.Abstractions; namespace ClaudeDo.Worker.Tests.External; /// Only the not-configured path — the import logic itself is covered by TicketImportAllTests. public sealed class TicketMcpToolsTests : IDisposable { private readonly DbFixture _db = new(); private readonly string _patDir = Path.Combine(Path.GetTempPath(), "cdo-ticketmcp-" + Guid.NewGuid().ToString("N")); public void Dispose() { _db.Dispose(); try { Directory.Delete(_patDir, recursive: true); } catch { /* best effort */ } } private TicketMcpTools Sut() { var config = new TicketSystemConfig(new WorkerConfig(), new TicketPatStore(new DpapiTokenStore(Path.Combine(_patDir, "ticket.pat")))); var clients = new TicketClientFactory(config, null!, NullLogger.Instance); var import = new TicketImportService(clients, _db.CreateFactory(), NullLogger.Instance); return new TicketMcpTools(import, new HubBroadcaster(new CapturingHubContext())); } [Fact] public async Task Candidates_report_unavailable_instead_of_throwing_when_not_configured() { var dto = await Sut().GetTicketImportCandidates(CancellationToken.None); Assert.False(dto.Available); Assert.False(string.IsNullOrWhiteSpace(dto.Reason)); Assert.Empty(dto.Items); } [Fact] public async Task Import_reports_skipped_instead_of_throwing_when_not_configured() { var result = await Sut().ImportTicket(1, null, CancellationToken.None); Assert.False(result.Created); Assert.False(string.IsNullOrWhiteSpace(result.SkippedReason)); } }