using System.Text.Json; using NucleicApp.Services; using NucleicApp.ViewModels; using NucleicProtocol.Interop; namespace NucleicApp.Tests; public sealed class PresentationModelTests { [Fact] public void HomeDerivesAttentionActivityAndCountsOnlyFromHostProjections() { using var store = new RendererStore(); store.Apply(""" {"input":[ {"archived":false,"disposition":"awaitingInput","pendingApprovalCount":0, "sessionID":"needs-input","status":"awaitingInput","title":"Needs input","updatedAt":3}, {"archived":false,"pendingApprovalCount":0, "sessionID":"running","status":"running","title":"Running","updatedAt":2}, {"archived":true,"pendingApprovalCount":1, "sessionID":"archived","status":"awaitingApproval","title":"Archived","updatedAt":4} ],"t":"sessionList"} """); store.Apply(""" {"activity":[],"counts":{"activeChats":2,"chats":3,"messages":8, "projects":1,"tokens":1200},"projects":[],"statusFeeds":[], "t":"dashboard","todos":[{"status":"open"},{"status":"done"}]} """); using var home = new HomeViewModel( store, () => new DateTimeOffset(2026, 8, 4, 9, 0, 0, TimeSpan.Zero)); Assert.Equal("Good morning", home.Greeting); Assert.Equal(new DashboardCounts(1, 2, 3, 8, 1200), home.Counts); Assert.Equal("2 active of 3 chats ยท 1 project", home.Subtitle); Assert.Equal("needs-input", Assert.Single(home.NeedsAttention).Id); Assert.Equal("running", Assert.Single(home.InProgress).Id); Assert.Equal(2, home.LiveSessions.Count); Assert.Equal(1, home.OpenTodoCount); } [Fact] public async Task ShellSessionLifecycleEmitsSubscriptionComposerAndApprovalIntents() { var directory = Directory.CreateTempSubdirectory("nucleic-shell-"); try { var rendezvousPath = Path.Combine(directory.FullName, "hostd.json"); await File.WriteAllTextAsync(rendezvousPath, JsonSerializer.Serialize( ProtocolInteropTests.ValidRendezvous())); var native = new FakeNativeProtocol { ProjectionResult = "{\"sessionID\":\"session-1\",\"splices\":[{\"deleteCount\":0,\"items\":[],\"start\":0}],\"version\":1}", }; using var store = new RendererStore(new ProtocolProjection(native)); store.Apply(TestRepository.Fixture("host", "sessionList")); store.Apply(TestRepository.Fixture("host", "approvalRequested")); var client = new FakeProtocolClient(); await using var connection = new HostConnection( store, Path.Combine(directory.FullName, "identity"), new FakeProtocolClientFactory(client), InlineRendererDispatcher.Instance); await connection.StartLocalAsync(rendezvousPath); using var shell = new ShellViewModel(store, connection); Assert.Equal(ProtocolResult.Ok, shell.OpenSession("session-1", 6)); var detail = Assert.IsType(shell.SelectedSession); Assert.Equal(ShellSection.Sessions, shell.Section); Assert.Equal("session-1", detail.Session?.Id); Assert.Equal("approval-1", Assert.Single(detail.PendingApprovals).Id); Assert.Equal(ProtocolResult.InvalidArgument, detail.SendText(" ")); Assert.Equal(ProtocolResult.Ok, detail.SendText("continue")); var approval = Assert.Single(detail.PendingApprovals); Assert.Equal(ProtocolResult.Ok, detail.Allow(approval)); Assert.Equal(ProtocolResult.Ok, detail.Deny(approval, "not now")); Assert.Equal(ProtocolResult.Ok, detail.AllowAlways(approval, ApprovalAlwaysScope.ToolName)); Assert.Equal(ProtocolResult.Ok, detail.CancelRun(approval)); store.Apply(TestRepository.Fixture("host", "pong")); Assert.NotNull(detail.LastProjection); shell.Navigate(ShellSection.Home); Assert.Null(shell.SelectedSession); Assert.Equal(ShellSection.Home, shell.Section); var messages = client.Intents.Select(ProtocolMessage.Parse).ToArray(); Assert.Equal( ["subscribe", "sendInput", "approvalRespond", "approvalRespond", "approvalRespond", "approvalRespond", "unsubscribe"], messages.Select(message => message.Tag)); Assert.Equal(6UL, messages[0].Root.GetProperty("sinceSeq").GetUInt64()); Assert.Equal("continue", messages[1].Root.GetProperty("input").GetProperty("parts")[0] .GetProperty("text").GetString()); Assert.Equal("allow", messages[2].Root.GetProperty("decision").GetProperty("type").GetString()); Assert.Equal("deny", messages[3].Root.GetProperty("decision").GetProperty("type").GetString()); Assert.Equal("not now", messages[3].Root.GetProperty("decision").GetProperty("reason").GetString()); Assert.Equal("allowAlways", messages[4].Root.GetProperty("decision").GetProperty("type").GetString()); Assert.Equal("toolName", messages[4].Root.GetProperty("decision").GetProperty("scope").GetString()); Assert.Equal("cancelRun", messages[5].Root.GetProperty("decision").GetProperty("type").GetString()); Assert.Equal("session-1", messages[6].Root.GetProperty("sessionID").GetString()); } finally { directory.Delete(recursive: true); } } }