Files
h-school/tests/HSchool.Server.Tests/SwarmUiSettingsStoreTests.cs

490 lines
19 KiB
C#

using System.Text.Json;
using HSchool.Server.Game;
namespace HSchool.Server.Tests;
public class SwarmUiSettingsStoreTests
{
[Fact]
public void Resolve_UsesAgeRulePreset()
{
var config = SwarmUiConfigFile.CreateDefault();
var profile = config.Resolve(8, PortraitKind.Avatar);
Assert.Equal("child", profile.PresetId);
Assert.Equal(SwarmUiModelDefinition.RealismV25Id, profile.Model);
Assert.Equal(8, profile.Steps);
Assert.Equal(1, profile.CfgScale);
Assert.Equal("euler", profile.Sampler);
Assert.Equal("simple", profile.Scheduler);
}
[Fact]
public void Resolve_FallsBackToActivePresetForAdults()
{
var config = SwarmUiConfigFile.CreateDefault();
var profile = config.Resolve(34, PortraitKind.Full);
Assert.Equal("default", profile.PresetId);
Assert.Equal(SwarmUiModelDefinition.RealismV25Id, profile.Model);
Assert.Equal(8, profile.Steps);
Assert.Equal(1, profile.CfgScale);
Assert.Equal("euler", profile.Sampler);
Assert.Equal("simple", profile.Scheduler);
}
[Fact]
public void Resolve_WithoutOverride_TakesStepsAndCfgFromModel()
{
var config = new SwarmUiConfigFile
{
Models =
[
new SwarmUiModelDefinition { Id = "m.safetensors", Steps = 9, CfgScale = 2.25 },
],
Presets =
[
new SwarmUiPresetDefinition
{
Id = "default",
Label = "Default",
Model = "m.safetensors",
Avatar = new SwarmUiKindPreset { Width = 512, Height = 512 },
Custom = new SwarmUiKindPreset { Width = 512, Height = 512 },
FullBody = new SwarmUiKindPreset { Width = 512, Height = 512 },
},
],
};
var profile = config.Resolve(20, PortraitKind.Avatar);
Assert.Equal(9, profile.Steps);
Assert.Equal(2.25, profile.CfgScale);
}
[Fact]
public void ToProfile_JoinsModelAndPresetNegative()
{
var model = new SwarmUiModelDefinition { Id = "m.safetensors", Negative = "model-neg" };
var preset = new SwarmUiPresetDefinition
{
Id = "default",
Label = "Default",
Model = "m.safetensors",
Negative = "preset-neg",
Avatar = new SwarmUiKindPreset { Width = 512, Height = 512 },
};
var profile = preset.ToProfile(PortraitKind.Avatar, model);
Assert.Equal("model-neg, preset-neg", profile.Negative);
}
[Fact]
public void Resolve_PresetOverrideBeatsModelDefaults()
{
var config = SwarmUiConfigFile.CreateDefault();
var preset = config.Presets[0];
preset.Steps = 12;
preset.CfgScale = 3.5;
var profile = config.Resolve(34, PortraitKind.Avatar);
Assert.Equal(12, profile.Steps);
Assert.Equal(3.5, profile.CfgScale);
Assert.Equal(0, profile.ClipSkip);
}
[Fact]
public void NormalizeAfterLoad_MigratesLegacyFlatFile()
{
var config = new SwarmUiConfigFile
{
Model = "legacy.safetensors",
Steps = 12,
Positive = "legacy prompt",
Avatar = new SwarmUiKindPreset { Width = 640, Height = 640, Positive = "close up." },
};
config.NormalizeAfterLoad();
Assert.Single(config.Presets);
Assert.Equal("default", config.Presets[0].Id);
Assert.Equal("legacy.safetensors", config.Presets[0].Model);
Assert.Equal("legacy prompt", config.Presets[0].Style);
Assert.Null(config.Presets[0].Positive);
Assert.Null(config.Presets[0].Steps);
var model = Assert.Single(config.Models, entry => entry.Id == "legacy.safetensors");
Assert.Equal(12, model.Steps);
Assert.Equal("close up.", config.Presets[0].Avatar!.ShotType);
Assert.Null(config.Presets[0].Avatar!.Positive);
}
[Fact]
public void NormalizeAfterLoad_LiftsLegacyPresetPositiveIntoStyleAndShotType()
{
var config = new SwarmUiConfigFile
{
Presets =
[
new SwarmUiPresetDefinition
{
Id = "default",
Label = "Default",
Model = "a.safetensors",
Steps = 6,
CfgScale = 2,
Positive = "was base prompt",
Negative = "old neg",
Avatar = new SwarmUiKindPreset { Width = 512, Height = 512, Positive = "was kind" },
Custom = new SwarmUiKindPreset { Width = 512, Height = 512 },
FullBody = new SwarmUiKindPreset { Width = 512, Height = 768 },
},
],
};
config.NormalizeAfterLoad();
Assert.Equal("was base prompt", config.Presets[0].Style);
Assert.Equal("was kind", config.Presets[0].Avatar!.ShotType);
Assert.Contains(config.Models, model => model.Id == "a.safetensors");
Assert.Equal("", Assert.Single(config.Models).Positive);
}
[Fact]
public void AllowedModelIds_IntersectsSwarmWithCatalog()
{
var config = SwarmUiConfigFile.CreateDefault();
var discovery = new SwarmUiDiscovery(
true,
[
SwarmUiModelDefinition.BabesId,
SwarmUiModelDefinition.MuseId,
"not-in-config.safetensors",
],
[],
[],
[],
[]);
var allowed = config.AllowedModelIds(discovery);
Assert.Equal([SwarmUiModelDefinition.MuseId], allowed);
Assert.DoesNotContain("not-in-config.safetensors", allowed);
Assert.DoesNotContain(SwarmUiModelDefinition.BabesId, allowed);
Assert.DoesNotContain(SwarmUiModelDefinition.DreamShaperId, allowed);
}
[Fact]
public void AllowedModelIds_WhenSwarmOffline_ReturnsCatalog()
{
var config = SwarmUiConfigFile.CreateDefault();
var allowed = config.AllowedModelIds(SwarmUiDiscovery.Offline);
Assert.Equal(config.Models.Select(model => model.Id), allowed);
}
[Fact]
public void Clone_IsIndependentOfTheSource()
{
var source = SwarmUiConfigFile.CreateDefault();
source.Presets[0].Model = "mutated.safetensors";
source.Models[0].Id = "mutated.safetensors";
var copy = SwarmUiConfigFile.Clone(source);
copy.Presets[0].Model = "other.safetensors";
Assert.Equal("mutated.safetensors", source.Presets[0].Model);
Assert.Equal("other.safetensors", copy.Presets[0].Model);
Assert.Equal(source.ActivePresetId, copy.ActivePresetId);
}
[Fact]
public void CreateDefault_SeedsCatalogModels()
{
var config = SwarmUiConfigFile.CreateDefault();
var ids = config.Models.Select(model => model.Id).ToList();
Assert.Equal(
[SwarmUiModelDefinition.MuseId, SwarmUiModelDefinition.RealismV25Id],
ids);
Assert.DoesNotContain(SwarmUiModelDefinition.BabesId, ids);
Assert.DoesNotContain(SwarmUiModelDefinition.DreamShaperId, ids);
Assert.DoesNotContain(SwarmUiModelDefinition.EpicRealismId, ids);
Assert.DoesNotContain(SwarmUiModelDefinition.LustifyId, ids);
Assert.DoesNotContain("realismByStableYogi_v20TurboGGUF.gguf", ids);
var realism = Assert.Single(config.Models, model => model.Id == SwarmUiModelDefinition.RealismV25Id);
Assert.Equal(8, realism.Steps);
Assert.Equal(1, realism.CfgScale);
Assert.Equal("euler", realism.Sampler);
Assert.Equal("simple", realism.Scheduler);
var muse = Assert.Single(config.Models, model => model.Id == SwarmUiModelDefinition.MuseId);
Assert.Equal(8, muse.Steps);
Assert.Equal(1, muse.CfgScale);
Assert.Equal("euler", muse.Sampler);
Assert.Equal("simple", muse.Scheduler);
var adult = Assert.Single(config.Presets, preset => preset.Id == "default");
Assert.Equal(SwarmUiModelDefinition.RealismV25Id, adult.Model);
Assert.DoesNotContain(":1.4)", adult.Negative, StringComparison.Ordinal);
Assert.Contains("photograph", adult.Style, StringComparison.OrdinalIgnoreCase);
var child = Assert.Single(config.Presets, preset => preset.Id == "child");
Assert.Equal(SwarmUiModelDefinition.RealismV25Id, child.Model);
var ageRule = Assert.Single(config.AgeRules);
Assert.Equal(6, ageRule.MinAge);
Assert.Equal(11, ageRule.MaxAge);
Assert.Equal("child", ageRule.PresetId);
}
[Theory]
[InlineData(6, "child")]
[InlineData(11, "child")]
[InlineData(5, "default")]
[InlineData(12, "default")]
public void Resolve_AgeRuleCoversSixThroughElevenInclusive(int age, string presetId)
{
var config = SwarmUiConfigFile.CreateDefault();
var profile = config.Resolve(age, PortraitKind.Avatar);
Assert.Equal(presetId, profile.PresetId);
Assert.Equal(SwarmUiModelDefinition.RealismV25Id, profile.Model);
}
[Fact]
public void CreateChild_StyleAndNegativeAreProseWithoutSdWeights()
{
var child = SwarmUiPresetDefinition.CreateChild();
Assert.NotNull(child.Avatar);
Assert.NotNull(child.FullBody);
Assert.DoesNotContain("(low quality", child.Style, StringComparison.Ordinal);
Assert.DoesNotContain(":1.4)", child.Style, StringComparison.Ordinal);
Assert.DoesNotContain("(low quality", child.Negative, StringComparison.Ordinal);
Assert.DoesNotContain(":1.4)", child.Negative, StringComparison.Ordinal);
Assert.DoesNotContain("(low quality", child.Avatar.ShotType, StringComparison.Ordinal);
Assert.DoesNotContain(":1.4)", child.Avatar.ShotType, StringComparison.Ordinal);
Assert.DoesNotContain("(low quality", child.FullBody.ShotType, StringComparison.Ordinal);
Assert.DoesNotContain(":1.4)", child.FullBody.ShotType, StringComparison.Ordinal);
}
[Fact]
public void SwarmUiJson_MatchesCatalogAndDefaultPresets()
{
var path = Path.Combine(RepoRoot(), "src", "HSchool.Server", "swarmui.json");
Assert.True(File.Exists(path), path);
var loaded = JsonSerializer.Deserialize<SwarmUiConfigFile>(File.ReadAllText(path), JsonOptions);
if (loaded is null)
{
Assert.Fail("swarmui.json did not deserialize.");
return;
}
loaded.NormalizeAfterLoad();
var expected = SwarmUiConfigFile.CreateDefault();
Assert.Equal(expected.Models.Select(model => model.Id), loaded.Models.Select(model => model.Id));
Assert.DoesNotContain(loaded.Models, model => model.Id.Contains("v20Turbo", StringComparison.OrdinalIgnoreCase));
for (var i = 0; i < expected.Models.Count; i++)
{
Assert.Equal(expected.Models[i].Label, loaded.Models[i].Label);
Assert.Equal(expected.Models[i].Steps, loaded.Models[i].Steps);
Assert.Equal(expected.Models[i].CfgScale, loaded.Models[i].CfgScale);
Assert.Equal(expected.Models[i].Sampler, loaded.Models[i].Sampler);
Assert.Equal(expected.Models[i].Scheduler, loaded.Models[i].Scheduler);
Assert.Equal(expected.Models[i].Seed, loaded.Models[i].Seed);
}
Assert.Equal(expected.Presets.Select(preset => preset.Id), loaded.Presets.Select(preset => preset.Id));
Assert.Equal(expected.Presets[0].Model, loaded.Presets[0].Model);
Assert.Equal(expected.Presets[0].Style, loaded.Presets[0].Style);
Assert.Equal(expected.Presets[0].Negative, loaded.Presets[0].Negative);
Assert.Equal(expected.Presets[0].Avatar?.ShotType, loaded.Presets[0].Avatar?.ShotType);
Assert.Equal(expected.Presets[1].Model, loaded.Presets[1].Model);
Assert.Equal(expected.Presets[1].Style, loaded.Presets[1].Style);
Assert.Equal(expected.Presets[1].Negative, loaded.Presets[1].Negative);
Assert.Equal(expected.Presets[1].Avatar?.ShotType, loaded.Presets[1].Avatar?.ShotType);
Assert.Equal(expected.Presets[1].FullBody?.ShotType, loaded.Presets[1].FullBody?.ShotType);
}
[Fact]
public void NormalizeAfterLoad_DoesNotRewriteExistingSdxlCatalog()
{
var living = new SwarmUiConfigFile
{
ActivePresetId = "default",
Models =
[
new SwarmUiModelDefinition
{
Id = SwarmUiModelDefinition.BabesId,
Label = "babes",
Steps = 7,
CfgScale = 1.5,
Sampler = "euler",
Scheduler = "normal",
},
new SwarmUiModelDefinition
{
Id = SwarmUiModelDefinition.DreamShaperId,
Label = "DreamShaper",
Steps = 4,
CfgScale = 2,
Sampler = "dpmpp_sde",
Scheduler = "karras",
},
],
Presets =
[
new SwarmUiPresetDefinition
{
Id = "default",
Label = "Default",
Model = SwarmUiModelDefinition.BabesId,
Avatar = new SwarmUiKindPreset { Width = 1024, Height = 1024 },
Custom = new SwarmUiKindPreset { Width = 896, Height = 1152 },
FullBody = new SwarmUiKindPreset { Width = 896, Height = 1152 },
},
new SwarmUiPresetDefinition
{
Id = "child",
Label = "Children",
Model = SwarmUiModelDefinition.DreamShaperId,
Avatar = new SwarmUiKindPreset { Width = 1024, Height = 1024 },
Custom = new SwarmUiKindPreset { Width = 896, Height = 1152 },
FullBody = new SwarmUiKindPreset { Width = 896, Height = 1152 },
},
],
AgeRules = [new SwarmUiAgeRule { MinAge = 6, MaxAge = 11, PresetId = "child" }],
};
living.NormalizeAfterLoad();
Assert.Equal(
[SwarmUiModelDefinition.BabesId, SwarmUiModelDefinition.DreamShaperId],
living.Models.Select(model => model.Id));
Assert.DoesNotContain(living.Models, model => model.Id == SwarmUiModelDefinition.MuseId);
Assert.DoesNotContain(living.Models, model => model.Id == SwarmUiModelDefinition.RealismV25Id);
Assert.Equal(SwarmUiModelDefinition.BabesId, living.Presets[0].Model);
Assert.Equal(SwarmUiModelDefinition.DreamShaperId, living.Presets[1].Model);
Assert.Equal("child", living.Resolve(8, PortraitKind.Avatar).PresetId);
Assert.Equal(SwarmUiModelDefinition.DreamShaperId, living.Resolve(8, PortraitKind.Avatar).Model);
}
[Fact]
public void NormalizeAfterLoad_EmptyModelsKeepSdxlPresetIdsInsteadOfKreaCatalog()
{
var living = new SwarmUiConfigFile
{
ActivePresetId = "default",
Presets =
[
new SwarmUiPresetDefinition
{
Id = "default",
Label = "Default",
Model = SwarmUiModelDefinition.BabesId,
Avatar = new SwarmUiKindPreset { Width = 1024, Height = 1024 },
Custom = new SwarmUiKindPreset { Width = 896, Height = 1152 },
FullBody = new SwarmUiKindPreset { Width = 896, Height = 1152 },
},
new SwarmUiPresetDefinition
{
Id = "child",
Label = "Children",
Model = SwarmUiModelDefinition.DreamShaperId,
Avatar = new SwarmUiKindPreset { Width = 1024, Height = 1024 },
Custom = new SwarmUiKindPreset { Width = 896, Height = 1152 },
FullBody = new SwarmUiKindPreset { Width = 896, Height = 1152 },
},
],
AgeRules = [new SwarmUiAgeRule { MinAge = 6, MaxAge = 11, PresetId = "child" }],
};
living.NormalizeAfterLoad();
Assert.Equal(
[SwarmUiModelDefinition.BabesId, SwarmUiModelDefinition.DreamShaperId],
living.Models.Select(model => model.Id));
Assert.DoesNotContain(living.Models, model => model.Id == SwarmUiModelDefinition.MuseId);
Assert.DoesNotContain(living.Models, model => model.Id == SwarmUiModelDefinition.RealismV25Id);
Assert.Equal(SwarmUiModelDefinition.BabesId, living.Presets[0].Model);
Assert.Equal(SwarmUiModelDefinition.DreamShaperId, living.Presets[1].Model);
Assert.Equal(SwarmUiModelDefinition.DreamShaperId, living.Resolve(8, PortraitKind.Avatar).Model);
}
[Fact]
public void Clone_DoesNotInjectKreaModelsIntoAnExistingSchoolCopy()
{
var living = new SwarmUiConfigFile
{
ActivePresetId = "default",
Models =
[
new SwarmUiModelDefinition
{
Id = SwarmUiModelDefinition.BabesId,
Steps = 7,
CfgScale = 1.5,
Sampler = "euler",
Scheduler = "normal",
},
new SwarmUiModelDefinition
{
Id = SwarmUiModelDefinition.DreamShaperId,
Steps = 4,
CfgScale = 2,
Sampler = "dpmpp_sde",
Scheduler = "karras",
},
],
Presets =
[
new SwarmUiPresetDefinition
{
Id = "default",
Label = "Default",
Model = SwarmUiModelDefinition.BabesId,
Avatar = new SwarmUiKindPreset { Width = 1024, Height = 1024 },
Custom = new SwarmUiKindPreset { Width = 896, Height = 1152 },
FullBody = new SwarmUiKindPreset { Width = 896, Height = 1152 },
},
],
};
var copy = SwarmUiConfigFile.Clone(living);
Assert.Equal(2, copy.Models.Count);
Assert.DoesNotContain(copy.Models, model => model.Id == SwarmUiModelDefinition.MuseId);
Assert.DoesNotContain(copy.Models, model => model.Id == SwarmUiModelDefinition.RealismV25Id);
Assert.Equal(SwarmUiModelDefinition.BabesId, copy.Presets[0].Model);
Assert.Equal("default", copy.ActivePresetId);
}
private static string RepoRoot()
{
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "h-school.sln")))
{
dir = dir.Parent;
}
if (dir is null)
{
throw new InvalidOperationException("Could not find h-school.sln from the test output directory.");
}
return dir.FullName;
}
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
};
}