feat(worker): TicketSystemConfig + ticket_api_base_url in worker.config.json
This commit is contained in:
@@ -57,6 +57,10 @@ public sealed class WorkerConfig
|
|||||||
[JsonPropertyName("usage_poll_interval_idle_seconds")]
|
[JsonPropertyName("usage_poll_interval_idle_seconds")]
|
||||||
public int UsagePollIntervalIdleSeconds { get; set; } = 900;
|
public int UsagePollIntervalIdleSeconds { get; set; } = 900;
|
||||||
|
|
||||||
|
/// <summary>Base URL of the Bandel ticket-system API. Null = ticket integration off entirely.</summary>
|
||||||
|
[JsonPropertyName("ticket_api_base_url")]
|
||||||
|
public string? TicketApiBaseUrl { get; set; }
|
||||||
|
|
||||||
public static string DefaultConfigPath =>
|
public static string DefaultConfigPath =>
|
||||||
Path.Combine(Paths.AppDataRoot(), "worker.config.json");
|
Path.Combine(Paths.AppDataRoot(), "worker.config.json");
|
||||||
|
|
||||||
@@ -103,6 +107,11 @@ public sealed class WorkerConfig
|
|||||||
public void SaveClaudeBin(string? path = null)
|
public void SaveClaudeBin(string? path = null)
|
||||||
=> SaveKey("claude_bin", JsonValue.Create(ClaudeBin), path);
|
=> SaveKey("claude_bin", JsonValue.Create(ClaudeBin), path);
|
||||||
|
|
||||||
|
/// <summary>Persistiert NUR <c>ticket_api_base_url</c>, gleiches read-modify-write wie
|
||||||
|
/// <see cref="SaveClaudeBin"/> — jedes andere Feld der Datei bleibt unangetastet.</summary>
|
||||||
|
public void SaveTicketApiBaseUrl(string? path = null)
|
||||||
|
=> SaveKey("ticket_api_base_url", JsonValue.Create(TicketApiBaseUrl), path);
|
||||||
|
|
||||||
private static void SaveKey(string key, JsonNode? value, string? path)
|
private static void SaveKey(string key, JsonNode? value, string? path)
|
||||||
{
|
{
|
||||||
path ??= DefaultConfigPath;
|
path ??= DefaultConfigPath;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using ClaudeDo.Worker.Planning;
|
|||||||
using ClaudeDo.Worker.Queue;
|
using ClaudeDo.Worker.Queue;
|
||||||
using ClaudeDo.Worker.Runner;
|
using ClaudeDo.Worker.Runner;
|
||||||
using ClaudeDo.Worker.State;
|
using ClaudeDo.Worker.State;
|
||||||
|
using ClaudeDo.Worker.Tickets;
|
||||||
using ClaudeDo.Worker.Online;
|
using ClaudeDo.Worker.Online;
|
||||||
using ClaudeDo.Worker.Online.Interfaces;
|
using ClaudeDo.Worker.Online.Interfaces;
|
||||||
using ClaudeDo.Worker.Prime;
|
using ClaudeDo.Worker.Prime;
|
||||||
@@ -215,6 +216,7 @@ builder.Services.AddSingleton(cfg.OnlineInbox);
|
|||||||
builder.Services.AddSingleton(new OnlineRefreshTokenStore(DpapiTokenStore.InAppData("online-inbox.token")));
|
builder.Services.AddSingleton(new OnlineRefreshTokenStore(DpapiTokenStore.InAppData("online-inbox.token")));
|
||||||
builder.Services.AddSingleton(new TicketPatStore(DpapiTokenStore.InAppData("ticket.pat")));
|
builder.Services.AddSingleton(new TicketPatStore(DpapiTokenStore.InAppData("ticket.pat")));
|
||||||
#pragma warning restore CA1416
|
#pragma warning restore CA1416
|
||||||
|
builder.Services.AddSingleton<TicketSystemConfig>();
|
||||||
|
|
||||||
if (cfg.OnlineInbox.Enabled)
|
if (cfg.OnlineInbox.Enabled)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using ClaudeDo.Worker.Config;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Worker.Tickets;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Der eine Ort, der beantwortet "ist die Ticketsystem-Anbindung eingerichtet?".
|
||||||
|
/// Liest die Base-URL aus dem WorkerConfig-Singleton (in-place aktualisiert vom Hub-Setter,
|
||||||
|
/// also ohne Neustart wirksam) und den PAT aus dem DPAPI-Store. Beide fehlen im Normalfall,
|
||||||
|
/// deshalb ist IsConfigured die billigste denkbare Abbruchbedingung: kein IO, keine DB.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TicketSystemConfig
|
||||||
|
{
|
||||||
|
private readonly WorkerConfig _worker;
|
||||||
|
private readonly TicketPatStore _pat;
|
||||||
|
|
||||||
|
public TicketSystemConfig(WorkerConfig worker, TicketPatStore pat)
|
||||||
|
{
|
||||||
|
_worker = worker;
|
||||||
|
_pat = pat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? BaseUrl => string.IsNullOrWhiteSpace(_worker.TicketApiBaseUrl)
|
||||||
|
? null
|
||||||
|
: _worker.TicketApiBaseUrl.Trim().TrimEnd('/');
|
||||||
|
|
||||||
|
public string? Token => _pat.Store.Read();
|
||||||
|
|
||||||
|
public bool IsConfigured => BaseUrl is not null && !string.IsNullOrEmpty(Token);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using ClaudeDo.Worker.Config;
|
||||||
|
using ClaudeDo.Worker.Tickets;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Worker.Tests.Tickets;
|
||||||
|
|
||||||
|
public sealed class TicketSystemConfigTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _dir = Path.Combine(Path.GetTempPath(), "cdo-tcfg-" + Guid.NewGuid().ToString("N"));
|
||||||
|
|
||||||
|
private TicketSystemConfig Make(string? baseUrl, string? pat)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(_dir);
|
||||||
|
var store = new DpapiTokenStore(Path.Combine(_dir, "ticket.pat"));
|
||||||
|
if (pat is not null) store.Save(pat);
|
||||||
|
var worker = new WorkerConfig { TicketApiBaseUrl = baseUrl };
|
||||||
|
return new TicketSystemConfig(worker, new TicketPatStore(store));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Not_configured_without_base_url()
|
||||||
|
=> Assert.False(Make(null, "tsp_x").IsConfigured);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Not_configured_without_pat()
|
||||||
|
=> Assert.False(Make("http://api.local", null).IsConfigured);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Configured_with_both()
|
||||||
|
=> Assert.True(Make("http://api.local", "tsp_x").IsConfigured);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Blank_base_url_counts_as_missing()
|
||||||
|
=> Assert.False(Make(" ", "tsp_x").IsConfigured);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Base_url_trailing_slash_is_trimmed()
|
||||||
|
=> Assert.Equal("http://api.local", Make("http://api.local/", "tsp_x").BaseUrl);
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (Directory.Exists(_dir)) Directory.Delete(_dir, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user