using ClaudeDo.Data.Models; namespace ClaudeDo.Data.Tests; public class ModelPresetsTests { [Fact] public void Defaults_cover_every_known_alias() { Assert.Equal( ModelRegistry.Aliases.OrderBy(a => a), ModelPresets.Defaults.Select(p => p.Model).OrderBy(m => m)); } [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] [InlineData("not json at all")] public void Parse_falls_back_to_defaults_when_unusable(string? json) { Assert.Equal(ModelPresets.Defaults, ModelPresets.Parse(json)); } [Fact] public void Serialize_roundtrips() { var edited = new[] { new ModelPreset("opus", "max", 55) }; var parsed = ModelPresets.Parse(ModelPresets.Serialize(edited)); Assert.Equal("max", ModelPresets.For(parsed, "opus").Effort); Assert.Equal(55, ModelPresets.For(parsed, "opus").MaxTurns); } [Fact] public void Serialize_fills_missing_aliases_from_defaults() { var parsed = ModelPresets.Parse(ModelPresets.Serialize(new[] { new ModelPreset("opus", "max", 55) })); Assert.Equal(ModelRegistry.Aliases.Count, parsed.Count); Assert.Equal(ModelPresets.For(ModelPresets.Defaults, "haiku"), ModelPresets.For(parsed, "haiku")); } [Theory] [InlineData("nonsense", 30)] // unknown effort → the default row's effort [InlineData("high", 0)] // turns out of range → the default row's turns [InlineData("high", 999)] public void Serialize_rejects_out_of_range_values(string effort, int turns) { var fallback = ModelPresets.For(ModelPresets.Defaults, "sonnet"); var parsed = ModelPresets.Parse(ModelPresets.Serialize(new[] { new ModelPreset("sonnet", effort, turns) })); var row = ModelPresets.For(parsed, "sonnet"); if (effort == "nonsense") Assert.Equal(fallback.Effort, row.Effort); if (turns is 0 or 999) Assert.Equal(fallback.MaxTurns, row.MaxTurns); } [Fact] public void For_unknown_model_yields_a_usable_row() { var row = ModelPresets.For(ModelPresets.Defaults, "some-future-model"); Assert.Equal("some-future-model", row.Model); Assert.Contains(row.Effort, EffortRegistry.Levels); Assert.True(row.MaxTurns > 0); } [Fact] public void For_unknown_model_uses_the_supplied_fallback_max_turns_instead_of_a_hardcoded_number() { var row = ModelPresets.For(ModelPresets.Defaults, "some-future-model", fallbackMaxTurns: 100); Assert.Equal(100, row.MaxTurns); } [Fact] public void For_a_full_model_id_resolves_to_its_alias_preset_row_not_the_unknown_fallback() { var sonnetPreset = ModelPresets.For(ModelPresets.Defaults, "sonnet"); var row = ModelPresets.For(ModelPresets.Defaults, "claude-sonnet-4-6"); Assert.Equal(sonnetPreset.Effort, row.Effort); Assert.Equal(sonnetPreset.MaxTurns, row.MaxTurns); } [Fact] public void For_a_full_model_id_honors_a_customized_preset_row_for_its_alias() { var parsed = ModelPresets.Parse(ModelPresets.Serialize(new[] { new ModelPreset("sonnet", "xhigh", 77) })); var row = ModelPresets.For(parsed, "claude-sonnet-4-6"); Assert.Equal("xhigh", row.Effort); Assert.Equal(77, row.MaxTurns); } [Fact] public void For_unknown_model_never_throws_and_still_starts_a_run() { var exception = Record.Exception(() => ModelPresets.For(ModelPresets.Defaults, "totally-unrecognized-model")); Assert.Null(exception); } [Fact] public void For_null_model_uses_the_default_alias() { Assert.Equal( ModelPresets.For(ModelPresets.Defaults, ModelRegistry.DefaultAlias), ModelPresets.For(ModelPresets.Defaults, null)); } [Theory] [InlineData("low", "low")] [InlineData("XHIGH", "xhigh")] [InlineData(" max ", "max")] public void NormalizeLevel_canonicalizes(string input, string expected) { Assert.Equal(expected, EffortRegistry.NormalizeLevel(input)); } [Fact] public void NormalizeLevel_blank_means_unset_and_unknown_throws() { Assert.Null(EffortRegistry.NormalizeLevel(null)); Assert.Throws(() => EffortRegistry.NormalizeLevel("gigantic")); } }