114 lines
5.6 KiB
C#
114 lines
5.6 KiB
C#
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<SessionDetailViewModel>(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); }
|
||
|
|
}
|
||
|
|
}
|