feat(config): add ConfigResolver with CLI>file>default precedence
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
30
src/ClaudeMailbox/Config/ConfigResolver.cs
Normal file
30
src/ClaudeMailbox/Config/ConfigResolver.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using ClaudeMailbox.Cli;
|
||||||
|
|
||||||
|
namespace ClaudeMailbox.Config;
|
||||||
|
|
||||||
|
public static class ConfigResolver
|
||||||
|
{
|
||||||
|
public static DaemonConfig Build(string[] serveArgs, FileConfig file)
|
||||||
|
{
|
||||||
|
var cliPort = ParseIntOption(serveArgs, "--port");
|
||||||
|
var cliBind = ClientCommands.GetOption(serveArgs, "--bind");
|
||||||
|
var cliDbPath = ClientCommands.GetOption(serveArgs, "--db-path");
|
||||||
|
|
||||||
|
var port = cliPort ?? file.Port ?? DaemonConfig.DefaultPort;
|
||||||
|
var bind = cliBind ?? file.Bind ?? DaemonConfig.DefaultBindAddress;
|
||||||
|
var dbPathRaw = cliDbPath ?? file.DbPath ?? Paths.DefaultDbPath();
|
||||||
|
|
||||||
|
return new DaemonConfig
|
||||||
|
{
|
||||||
|
Port = port,
|
||||||
|
BindAddress = bind,
|
||||||
|
DbPath = Paths.Expand(dbPathRaw),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int? ParseIntOption(string[] args, string name)
|
||||||
|
{
|
||||||
|
var raw = ClientCommands.GetOption(args, name);
|
||||||
|
return int.TryParse(raw, out var v) ? v : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
59
tests/ClaudeMailbox.Tests/Config/ConfigResolverTests.cs
Normal file
59
tests/ClaudeMailbox.Tests/Config/ConfigResolverTests.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using ClaudeMailbox.Config;
|
||||||
|
|
||||||
|
namespace ClaudeMailbox.Tests.Config;
|
||||||
|
|
||||||
|
public sealed class ConfigResolverTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CliFlag_WinsOverFile()
|
||||||
|
{
|
||||||
|
var file = new FileConfig { Port = 1000 };
|
||||||
|
var cfg = ConfigResolver.Build(new[] { "--port", "9999" }, file);
|
||||||
|
Assert.Equal(9999, cfg.Port);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void File_WinsOverDefault()
|
||||||
|
{
|
||||||
|
var file = new FileConfig { Port = 1000, Bind = "0.0.0.0", DbPath = "/tmp/x.db" };
|
||||||
|
var cfg = ConfigResolver.Build(Array.Empty<string>(), file);
|
||||||
|
Assert.Equal(1000, cfg.Port);
|
||||||
|
Assert.Equal("0.0.0.0", cfg.BindAddress);
|
||||||
|
Assert.Equal(Paths.Expand("/tmp/x.db"), cfg.DbPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Default_UsedWhenNeitherCliNorFile()
|
||||||
|
{
|
||||||
|
var cfg = ConfigResolver.Build(Array.Empty<string>(), new FileConfig());
|
||||||
|
Assert.Equal(DaemonConfig.DefaultPort, cfg.Port);
|
||||||
|
Assert.Equal(DaemonConfig.DefaultBindAddress, cfg.BindAddress);
|
||||||
|
Assert.Equal(Paths.DefaultDbPath(), cfg.DbPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Mixed_CliPort_FileDbPath_DefaultBind()
|
||||||
|
{
|
||||||
|
var file = new FileConfig { DbPath = "/tmp/mixed.db" };
|
||||||
|
var cfg = ConfigResolver.Build(new[] { "--port", "7000" }, file);
|
||||||
|
Assert.Equal(7000, cfg.Port);
|
||||||
|
Assert.Equal(DaemonConfig.DefaultBindAddress, cfg.BindAddress);
|
||||||
|
Assert.Equal(Paths.Expand("/tmp/mixed.db"), cfg.DbPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CliDbPath_ExpandsEnvVars()
|
||||||
|
{
|
||||||
|
var file = new FileConfig();
|
||||||
|
var cfg = ConfigResolver.Build(new[] { "--db-path", "~/foo.db" }, file);
|
||||||
|
Assert.DoesNotContain("~", cfg.DbPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvalidPortFlag_FallsBackToFileOrDefault()
|
||||||
|
{
|
||||||
|
var file = new FileConfig { Port = 4242 };
|
||||||
|
var cfg = ConfigResolver.Build(new[] { "--port", "not-a-number" }, file);
|
||||||
|
Assert.Equal(4242, cfg.Port);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user