using HSchool.Content; using HSchool.Server.Api; using HSchool.Server.Game; namespace HSchool.Server.Tests; public class PortraitPromptBuilderTests { [Fact] public void Build_DescribesAppearanceAndClothingInProse() { var profile = SamplePreset().ToProfile(PortraitKind.Avatar); var card = SampleCard(); var (positive, _) = PortraitPromptBuilder.Build(card, profile, PortraitKind.Avatar); Assert.Contains("12 years old", positive, StringComparison.Ordinal); Assert.Contains("with black hair", positive, StringComparison.OrdinalIgnoreCase); Assert.Contains("wearing a white shirt", positive, StringComparison.OrdinalIgnoreCase); Assert.Contains("Head and shoulders.", positive, StringComparison.Ordinal); Assert.DoesNotContain("age 12", positive, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("Hair colour", positive, StringComparison.Ordinal); Assert.DoesNotContain("wearing shirt in white", positive, StringComparison.OrdinalIgnoreCase); } [Fact] public void Build_FullBody_AddsFullBodyPreset() { var preset = SamplePreset(); var (avatarPositive, _) = PortraitPromptBuilder.Build(SampleCard(), preset.ToProfile(PortraitKind.Avatar), PortraitKind.Avatar); var (fullPositive, _) = PortraitPromptBuilder.Build(SampleCard(), preset.ToProfile(PortraitKind.Full), PortraitKind.Full); Assert.Contains("Head and shoulders.", avatarPositive, StringComparison.Ordinal); Assert.DoesNotContain("Standing full body.", avatarPositive, StringComparison.Ordinal); Assert.Contains("Standing full body.", fullPositive, StringComparison.Ordinal); } [Fact] public void Build_Custom_KeepsShotTypeAndAppendsPromptExtra() { var profile = SamplePreset().ToProfile(PortraitKind.Custom); const string extra = "standing in a school hallway, soft window light"; var (customPositive, _) = PortraitPromptBuilder.Build( SampleCard(), profile, PortraitKind.Custom, extra); Assert.Contains("School photo.", customPositive, StringComparison.Ordinal); Assert.Contains("Hallway light.", customPositive, StringComparison.Ordinal); Assert.Contains(extra, customPositive, StringComparison.Ordinal); Assert.DoesNotContain("Head and shoulders.", customPositive, StringComparison.Ordinal); Assert.DoesNotContain("Standing full body.", customPositive, StringComparison.Ordinal); Assert.True( customPositive.IndexOf("Hallway light.", StringComparison.Ordinal) < customPositive.IndexOf(extra, StringComparison.Ordinal)); } [Fact] public void Build_OrdersLayers_ModelStyleShotAppearanceClothing() { var preset = SamplePreset(); var model = new SwarmUiModelDefinition { Id = preset.Model, Positive = "Model base." }; var profile = preset.ToProfile(PortraitKind.Avatar, model); var (positive, _) = PortraitPromptBuilder.Build(SampleCard(), profile, PortraitKind.Avatar); var modelAt = positive.IndexOf("Model base.", StringComparison.Ordinal); var styleAt = positive.IndexOf("School photo.", StringComparison.Ordinal); var shotAt = positive.IndexOf("Head and shoulders.", StringComparison.Ordinal); var subjectAt = positive.IndexOf("teenage girl", StringComparison.OrdinalIgnoreCase); var clothesAt = positive.IndexOf("wearing a white shirt", StringComparison.OrdinalIgnoreCase); Assert.True(modelAt >= 0 && styleAt > modelAt && shotAt > styleAt && subjectAt > shotAt && clothesAt > subjectAt); Assert.DoesNotContain("pose", positive, StringComparison.OrdinalIgnoreCase); } [Fact] public void Build_ChildAge_UsesChildSubjectDescriptor() { var profile = SamplePreset().ToProfile(PortraitKind.Avatar); var child = SampleCard() with { Age = 8 }; var (positive, _) = PortraitPromptBuilder.Build(child, profile, PortraitKind.Avatar); Assert.Contains("young girl", positive, StringComparison.OrdinalIgnoreCase); Assert.Contains("8 years old", positive, StringComparison.Ordinal); } [Fact] public void Build_TornClothing_PrefixesCondition() { var profile = SamplePreset().ToProfile(PortraitKind.Avatar); var card = SampleCard() with { Worn = [ new WornItemResponse( "Shirt", "Shirt", "White", "White", [new DefLabelResponse(ApparelLayers.Top, "Top")], 0.2f, "torn", [ApparelLayers.Underwear]), ], }; var (positive, _) = PortraitPromptBuilder.Build(card, profile, PortraitKind.Avatar); Assert.Contains("wearing a torn white shirt", positive, StringComparison.OrdinalIgnoreCase); } [Fact] public void Build_AppendsSceneAfterClothing() { var profile = SamplePreset().ToProfile(PortraitKind.Avatar); var scene = new PortraitScene( ["a classroom blackboard with chalk notes"], [], [], [], [new SwarmUiLoraEntry { Name = "winter-embed", Weight = 1 }], []); var (positive, _) = PortraitPromptBuilder.Build(SampleCard(), profile, PortraitKind.Avatar, scene: scene); var clothesAt = positive.IndexOf("wearing a white shirt", StringComparison.OrdinalIgnoreCase); var sceneAt = positive.IndexOf("a classroom blackboard with chalk notes", StringComparison.Ordinal); Assert.True(clothesAt >= 0 && sceneAt > clothesAt); Assert.Contains("", positive, StringComparison.Ordinal); Assert.DoesNotContain("snow.safetensors", positive, StringComparison.Ordinal); } [Fact] public void Build_DoesNotPutSceneLoraInText_ApplySceneAddsLoraParams() { var profile = SamplePreset().ToProfile(PortraitKind.Avatar); var scene = new PortraitScene( ["a winter prop"], [], [new SwarmUiLoraEntry { Name = "snow.safetensors", Weight = 0.7 }], [], [], []); var (positive, _) = PortraitPromptBuilder.Build(SampleCard(), profile, PortraitKind.Avatar, scene: scene); var withLoras = PortraitPromptBuilder.ApplyScene(profile, scene); Assert.Contains("a winter prop", positive, StringComparison.Ordinal); Assert.DoesNotContain("snow.safetensors", positive, StringComparison.Ordinal); Assert.DoesNotContain(" entry.Name == "snow.safetensors"); } [Fact] public void Build_PromptAndGenerateShareTheSameAssembly() { var profile = SamplePreset().ToProfile(PortraitKind.Avatar); var scene = new PortraitScene( ["a classroom blackboard with chalk notes"], ["messy desk"], [new SwarmUiLoraEntry { Name = "snow.safetensors", Weight = 0.7 }], [], [new SwarmUiLoraEntry { Name = "winter-embed", Weight = 1 }], []); var prompt = PortraitPromptBuilder.Build(SampleCard(), profile, PortraitKind.Avatar, scene: scene); var generate = PortraitPromptBuilder.Build(SampleCard(), profile, PortraitKind.Avatar, scene: scene); Assert.Equal(prompt, generate); Assert.Contains("", prompt.Positive, StringComparison.Ordinal); Assert.Contains("messy desk", prompt.Negative, StringComparison.Ordinal); } private static SwarmUiPresetDefinition SamplePreset() { var preset = SwarmUiPresetDefinition.CreateDefault(); preset.Style = "School photo."; preset.Avatar = new SwarmUiKindPreset { Width = 512, Height = 512, ShotType = "Head and shoulders.", }; preset.Custom = new SwarmUiKindPreset { Width = 512, Height = 768, ShotType = "Hallway light.", }; preset.FullBody = new SwarmUiKindPreset { Width = 512, Height = 768, ShotType = "Standing full body.", }; return preset; } private static PersonCardResponse SampleCard() => new( "f0.c0", "Maria Ivanova", "Ivanova", "Maria", "", true, 12, new DateTime(2000, 3, 14, 0, 0, 0, DateTimeKind.Utc), ["student"], 5, "A", "class-1", null, null, [new LabeledStatResponse("HairColor", "Hair colour", "Black")], [], [], [], null, null, new PersonFamilyResponse([], [], [], []), [new WornItemResponse("Shirt", "Shirt", "White", "White", [new DefLabelResponse(ApparelLayers.Top, "Top")], 1f, "new", [ApparelLayers.Underwear])], [], 0f, 0f); }