using Admin.NET.Plugin.AiDOP.Entity; using Admin.NET.Plugin.AiDOP.SmartOps; using Xunit; namespace Admin.NET.Plugin.AiDOP.Tests.SmartOps; public class SmartOpsImprovementActionTests { [Theory] [InlineData("doing", "in_progress")] [InlineData("in_progress", "in_progress")] [InlineData("done", "completed")] [InlineData("cancelled", "cancelled")] [InlineData("pending", "pending")] public void Status_NormalizesLegacyValues(string raw, string expected) { Assert.Equal(expected, SmartOpsImprovementActionStatus.Normalize(raw)); } [Theory] [InlineData("pending", "in_progress", true)] [InlineData("pending", "cancelled", true)] [InlineData("in_progress", "completed", true)] [InlineData("completed", "pending", false)] [InlineData("cancelled", "completed", false)] [InlineData("cancelled", "pending", true)] public void Status_TransitionsAreGuarded(string from, string to, bool allowed) { Assert.Equal(allowed, SmartOpsImprovementActionStatus.CanTransition(from, to)); } [Fact] public void Overdue_OnlyAppliesToOpenActions() { var yesterday = DateTime.Today.AddDays(-1); Assert.True(SmartOpsImprovementActionStatus.IsOverdue("pending", yesterday, DateTime.Today)); Assert.False(SmartOpsImprovementActionStatus.IsOverdue("completed", yesterday, DateTime.Today)); Assert.Equal("overdue", SmartOpsImprovementActionStatus.DisplayStatus("doing", yesterday, DateTime.Today)); } [Fact] public void NoticeKey_IsIdempotentPerDayAndType() { var key1 = SmartOpsImprovementNoticeType.NoticeKey(1, 9, SmartOpsImprovementNoticeType.Overdue, new DateTime(2026, 8, 17)); var key2 = SmartOpsImprovementNoticeType.NoticeKey(1, 9, SmartOpsImprovementNoticeType.Overdue, new DateTime(2026, 8, 17)); var key3 = SmartOpsImprovementNoticeType.NoticeKey(1, 9, SmartOpsImprovementNoticeType.DueSoon, new DateTime(2026, 8, 17)); Assert.Equal(key1, key2); Assert.NotEqual(key1, key3); } [Fact] public void LegacyJson_ParsesCamelAndPascal() { var camel = SmartOpsImprovementActionService.ParseDrafts( """[{"content":"核对缺料","owner":"1001","dueDate":"2026-08-18","status":"doing"}]"""); var pascal = SmartOpsImprovementActionService.ParseDrafts( """[{"Content":"核对缺料","Owner":"计划员","DueDate":"2026-08-18","Status":"pending"}]"""); Assert.Single(camel); Assert.Equal("核对缺料", camel[0].Content); Assert.Equal("doing", camel[0].Status); Assert.Single(pascal); Assert.Equal("计划员", pascal[0].Owner); } [Fact] public void Controller_NoLongerWritesActionJsonAsSourceOfTruth() { var source = File.ReadAllText(FindController()); Assert.Contains("_actions.MutateAsync", source); Assert.Contains("_actions.ReplaceFromDraftAsync", source); Assert.DoesNotContain("ActionItemsJson = actionItemsJson", source); } private static string FindController() { var dir = new DirectoryInfo(AppContext.BaseDirectory); while (dir != null) { var candidate = Path.Combine(dir.FullName, "server", "Plugins", "Admin.NET.Plugin.AiDOP", "Controllers", "AdoSmartOpsImprovementPlanController.cs"); if (File.Exists(candidate)) return candidate; dir = dir.Parent; } throw new FileNotFoundException("AdoSmartOpsImprovementPlanController.cs"); } }