From 632a220e148041d771f22b96440ddefc38f2b710 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 25 May 2026 01:32:19 +0000 Subject: [PATCH] test(tray): expand ActivityStreamService coverage with 20 new tests Add tests covering previously untested behaviour in ActivityStreamService: - Category defaults to "general" when null or whitespace input - Whitespace/empty title is rejected (no-op Add) - Insertion order (newest-first) - GetItems maxItems limiting and zero-items edge case - GetItems category filtering: exact match, dot-prefix sub-categories, case-insensitive matching, and non-match guard - 'all' category selector returns every item - Clear and Add both raise the Updated event - BuildSupportBundle maxItems parameter limits output - BuildSupportBundle includes session= and node= fields when set - BuildSupportBundle truncates long node IDs to 16 chars + '...' - BuildSupportBundle omits session/node fields when not provided Total: 22 tests (was 2). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ActivityStreamServiceTests.cs | 258 ++++++++++++++++++ 1 file changed, 258 insertions(+) diff --git a/tests/OpenClaw.Tray.Tests/ActivityStreamServiceTests.cs b/tests/OpenClaw.Tray.Tests/ActivityStreamServiceTests.cs index 128571bf6..8ef444c61 100644 --- a/tests/OpenClaw.Tray.Tests/ActivityStreamServiceTests.cs +++ b/tests/OpenClaw.Tray.Tests/ActivityStreamServiceTests.cs @@ -51,4 +51,262 @@ public void BuildSupportBundle_DefaultIncludesStoredActivityWindow() Assert.Contains("bundle-item-399", bundle); Assert.Contains("bundle-item-000", bundle); } + + // ── Add: field defaults ── + + [Fact] + public void Add_WithAllOptionalFields_StoresFieldValues() + { + ActivityStreamService.Add( + "sessions", + "My Session", + details: "some detail", + icon: "🔥", + dashboardPath: "/dashboard/abc", + sessionKey: "sk-123", + nodeId: "node-xyz"); + + var items = ActivityStreamService.GetItems(); + + Assert.Single(items); + var item = items[0]; + Assert.Equal("sessions", item.Category); + Assert.Equal("My Session", item.Title); + Assert.Equal("some detail", item.Details); + Assert.Equal("🔥", item.Icon); + Assert.Equal("/dashboard/abc", item.DashboardPath); + Assert.Equal("sk-123", item.SessionKey); + Assert.Equal("node-xyz", item.NodeId); + } + + [Fact] + public void Add_WithNullCategory_DefaultsToGeneral() + { + ActivityStreamService.Add(null!, "Title"); + + var items = ActivityStreamService.GetItems(); + + Assert.Equal("general", items[0].Category); + } + + [Fact] + public void Add_WithWhitespaceCategory_DefaultsToGeneral() + { + ActivityStreamService.Add(" ", "Title"); + + var items = ActivityStreamService.GetItems(); + + Assert.Equal("general", items[0].Category); + } + + [Fact] + public void Add_WithEmptyTitle_DoesNotAddItem() + { + ActivityStreamService.Add("test", ""); + + var items = ActivityStreamService.GetItems(); + + Assert.Empty(items); + } + + [Fact] + public void Add_WithWhitespaceTitle_DoesNotAddItem() + { + ActivityStreamService.Add("test", " "); + + var items = ActivityStreamService.GetItems(); + + Assert.Empty(items); + } + + [Fact] + public void Add_StoresItemsNewestFirst() + { + ActivityStreamService.Add("test", "first"); + ActivityStreamService.Add("test", "second"); + + var items = ActivityStreamService.GetItems(); + + Assert.Equal("second", items[0].Title); + Assert.Equal("first", items[1].Title); + } + + // ── GetItems: filtering and limiting ── + + [Fact] + public void GetItems_WithMaxItemsLessThanStored_ReturnsOnlyNewest() + { + ActivityStreamService.Add("test", "old"); + ActivityStreamService.Add("test", "newer"); + ActivityStreamService.Add("test", "newest"); + + var items = ActivityStreamService.GetItems(maxItems: 2); + + Assert.Equal(2, items.Count); + Assert.Equal("newest", items[0].Title); + Assert.Equal("newer", items[1].Title); + } + + [Fact] + public void GetItems_WithMaxItemsZero_ReturnsEmpty() + { + ActivityStreamService.Add("test", "item"); + + var items = ActivityStreamService.GetItems(maxItems: 0); + + Assert.Empty(items); + } + + [Fact] + public void GetItems_WithCategoryFilter_ReturnsOnlyMatchingCategory() + { + ActivityStreamService.Add("sessions", "session item"); + ActivityStreamService.Add("nodes", "node item"); + ActivityStreamService.Add("sessions", "another session"); + + var items = ActivityStreamService.GetItems(category: "sessions"); + + Assert.Equal(2, items.Count); + Assert.All(items, item => Assert.Equal("sessions", item.Category)); + } + + [Fact] + public void GetItems_WithCategoryAll_ReturnsEverything() + { + ActivityStreamService.Add("sessions", "s"); + ActivityStreamService.Add("nodes", "n"); + + var items = ActivityStreamService.GetItems(category: "all"); + + Assert.Equal(2, items.Count); + } + + [Fact] + public void GetItems_CategoryFilter_IsCaseInsensitive() + { + ActivityStreamService.Add("Sessions", "uppercase S"); + + var items = ActivityStreamService.GetItems(category: "sessions"); + + Assert.Single(items); + } + + [Fact] + public void GetItems_CategoryFilter_MatchesDotPrefixSubcategories() + { + ActivityStreamService.Add("sessions.chat", "chat session"); + ActivityStreamService.Add("sessions.mcp", "mcp session"); + ActivityStreamService.Add("nodes", "unrelated"); + + var items = ActivityStreamService.GetItems(category: "sessions"); + + Assert.Equal(2, items.Count); + Assert.DoesNotContain(items, item => item.Category == "nodes"); + } + + [Fact] + public void GetItems_CategoryFilter_DoesNotMatchPartialPrefix() + { + ActivityStreamService.Add("sessionsExtra", "should not match"); + ActivityStreamService.Add("sessions.sub", "should match"); + + var items = ActivityStreamService.GetItems(category: "sessions"); + + Assert.Single(items); + Assert.Equal("sessions.sub", items[0].Category); + } + + // ── Clear ── + + [Fact] + public void Clear_RemovesAllItems() + { + ActivityStreamService.Add("test", "item1"); + ActivityStreamService.Add("test", "item2"); + + ActivityStreamService.Clear(); + + Assert.Empty(ActivityStreamService.GetItems()); + } + + [Fact] + public void Clear_RaisesUpdatedEvent() + { + var raised = false; + EventHandler handler = (_, _) => raised = true; + ActivityStreamService.Updated += handler; + try + { + ActivityStreamService.Clear(); + Assert.True(raised); + } + finally + { + ActivityStreamService.Updated -= handler; + } + } + + [Fact] + public void Add_RaisesUpdatedEvent() + { + var raised = false; + EventHandler handler = (_, _) => raised = true; + ActivityStreamService.Updated += handler; + try + { + ActivityStreamService.Add("test", "item"); + Assert.True(raised); + } + finally + { + ActivityStreamService.Updated -= handler; + } + } + + // ── BuildSupportBundle ── + + [Fact] + public void BuildSupportBundle_WithMaxItemsParam_LimitsOutput() + { + for (var i = 0; i < 10; i++) + ActivityStreamService.Add("test", $"item-{i}"); + + var bundle = ActivityStreamService.BuildSupportBundle(maxItems: 3); + + Assert.Contains("Items: 3", bundle); + } + + [Fact] + public void BuildSupportBundle_IncludesSessionKeyAndNodeIdWhenPresent() + { + ActivityStreamService.Add("test", "title", sessionKey: "sk-abc", nodeId: "nd-xyz"); + + var bundle = ActivityStreamService.BuildSupportBundle(); + + Assert.Contains("session=sk-abc", bundle); + Assert.Contains("node=nd-xyz", bundle); + } + + [Fact] + public void BuildSupportBundle_TruncatesLongNodeId() + { + var longNodeId = new string('a', 32); + ActivityStreamService.Add("test", "title", nodeId: longNodeId); + + var bundle = ActivityStreamService.BuildSupportBundle(); + + Assert.Contains("node=" + new string('a', 16) + "...", bundle); + Assert.DoesNotContain("node=" + longNodeId, bundle); + } + + [Fact] + public void BuildSupportBundle_OmitsSessionAndNodeFieldsWhenAbsent() + { + ActivityStreamService.Add("test", "plain item"); + + var bundle = ActivityStreamService.BuildSupportBundle(); + + Assert.DoesNotContain("session=", bundle); + Assert.DoesNotContain("node=", bundle); + } }