feat: ship agent-first mission control v0.2.57
CI - Build & Test / Backend (.NET) (push) Successful in 42s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 2m46s
CI - Build & Test / Security Check (push) Successful in 3s
CI - Build & Test / Deploy Nexus (push) Successful in 56s

This commit is contained in:
AzuTear
2026-07-31 22:39:47 +02:00
parent 3bc7622977
commit f5552218bc
535 changed files with 95242 additions and 8791 deletions
+222
View File
@@ -0,0 +1,222 @@
using System.Text.Json;
using System.Text;
using System.Threading.Channels;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using Nexus.Api.Controllers;
using Nexus.Api.Data;
using Nexus.Api.Models;
using Nexus.Api.Repositories;
using Nexus.Api.Services;
using Xunit;
namespace Nexus.Api.Tests;
public sealed class DomainEventOutboxTests
{
[Fact]
public async Task Task_mutation_persists_content_minimized_outbox_event()
{
await using var db = CreateDatabase();
var repository = new TaskRepository(db);
var task = new WorkTask
{
Title = "Sensitive title that must not enter the event payload",
Detail = "Sensitive details",
AssignedTo = "programmer"
};
await repository.AddAsync(task);
var created = await db.OutboxEvents.SingleAsync();
Assert.Equal("task.created", created.Type);
Assert.Equal("task", created.AggregateType);
Assert.Equal(task.Id.ToString(), created.AggregateId);
Assert.DoesNotContain(task.Title, created.PayloadJson, StringComparison.Ordinal);
Assert.DoesNotContain(task.Detail, created.PayloadJson, StringComparison.Ordinal);
using var payload = JsonDocument.Parse(created.PayloadJson);
Assert.Equal(task.Id, payload.RootElement.GetProperty("id").GetGuid());
Assert.Equal("Backlog", payload.RootElement.GetProperty("state").GetString());
task.State = "In progress";
await repository.UpdateAsync(task);
var events = await db.OutboxEvents
.OrderBy(item => item.Sequence)
.ToListAsync();
Assert.Equal(2, events.Count);
Assert.Equal("task.updated", events[1].Type);
using var updatedPayload = JsonDocument.Parse(events[1].PayloadJson);
Assert.Equal(
"In progress",
updatedPayload.RootElement.GetProperty("state").GetString());
}
[Fact]
public async Task Activity_event_keeps_message_content_out_of_domain_stream()
{
await using var db = CreateDatabase();
var repository = new ActivityRepository(db, new LiveUpdateService());
var taskId = Guid.NewGuid();
const string sensitiveMessage = "Agent programmer handled private workspace details";
await repository.AddAsync(new ActivityEvent
{
Type = "comment",
Message = sensitiveMessage,
TaskId = taskId
});
var stored = await db.OutboxEvents.SingleAsync();
Assert.Equal("activity.created", stored.Type);
Assert.Equal("activity", stored.AggregateType);
Assert.DoesNotContain(sensitiveMessage, stored.PayloadJson, StringComparison.Ordinal);
using var payload = JsonDocument.Parse(stored.PayloadJson);
Assert.Equal(taskId, payload.RootElement.GetProperty("taskId").GetGuid());
Assert.Equal("comment", payload.RootElement.GetProperty("type").GetString());
}
[Fact]
public void Outbox_mapper_normalizes_entity_reference_and_clones_payload()
{
var stored = new OutboxEvent
{
Sequence = 42,
Type = "agent.proposal.created",
AggregateType = "AgentProposal",
AggregateId = Guid.NewGuid().ToString(),
AggregateRevision = 3,
PayloadJson = """{"state":"awaiting_approval"}"""
};
var mapped = DomainEventStreamService.Map(stored);
Assert.Equal(42, mapped.Sequence);
Assert.Equal("agent-proposal", mapped.Entity.Type);
Assert.Equal(3, mapped.EntityRevision);
Assert.Equal(
"awaiting_approval",
mapped.Payload.GetProperty("state").GetString());
}
[Fact]
public async Task Resync_required_advances_sse_cursor_to_current_sequence()
{
await using var db = CreateDatabase();
db.OutboxEvents.Add(new OutboxEvent
{
Sequence = 10,
Type = "task.updated",
AggregateType = "task",
AggregateId = Guid.NewGuid().ToString(),
PayloadJson = """{"state":"Backlog"}""",
PublishedAt = DateTimeOffset.UtcNow
});
await db.SaveChangesAsync();
var stream = new FixedDomainEventStream(currentSequence: 25);
var controller = new DomainEventsController(
db,
stream,
NullLogger<DomainEventsController>.Instance);
var body = new MemoryStream();
controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext()
};
controller.Response.Body = body;
await controller.Get(
channels: null,
afterSequence: 1,
CancellationToken.None);
body.Position = 0;
var content = await new StreamReader(
body,
Encoding.UTF8,
leaveOpen: true).ReadToEndAsync();
Assert.Contains("id: 25", content, StringComparison.Ordinal);
Assert.Contains("\"eventType\":\"resync_required\"", content, StringComparison.Ordinal);
Assert.Contains("\"resumeSequence\":25", content, StringComparison.Ordinal);
}
[Fact]
public async Task Replay_exposes_only_events_committed_as_published()
{
await using var db = CreateDatabase();
var entityId = Guid.NewGuid().ToString();
db.OutboxEvents.AddRange(
new OutboxEvent
{
Sequence = 1,
Type = "task.updated",
AggregateType = "task",
AggregateId = entityId,
PayloadJson = """{"state":"Review"}""",
PublishedAt = DateTimeOffset.UtcNow
},
new OutboxEvent
{
Sequence = 2,
Type = "task.updated",
AggregateType = "task",
AggregateId = entityId,
PayloadJson = """{"state":"Done"}"""
});
await db.SaveChangesAsync();
var controller = new DomainEventsController(
db,
new FixedDomainEventStream(currentSequence: 2),
NullLogger<DomainEventsController>.Instance);
var body = new MemoryStream();
controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext()
};
controller.Response.Body = body;
await controller.Get(
channels: null,
afterSequence: 0,
CancellationToken.None);
body.Position = 0;
var content = await new StreamReader(
body,
Encoding.UTF8,
leaveOpen: true).ReadToEndAsync();
Assert.Contains("id: 1", content, StringComparison.Ordinal);
Assert.Contains("\"state\":\"Review\"", content, StringComparison.Ordinal);
Assert.DoesNotContain("id: 2", content, StringComparison.Ordinal);
Assert.DoesNotContain("\"state\":\"Done\"", content, StringComparison.Ordinal);
}
private static NexusDbContext CreateDatabase()
{
var options = new DbContextOptionsBuilder<NexusDbContext>()
.UseInMemoryDatabase($"domain-outbox-{Guid.NewGuid():N}")
.Options;
return new NexusDbContext(options);
}
private sealed class FixedDomainEventStream(long currentSequence)
: IDomainEventStreamService
{
public long CurrentSequence { get; } = currentSequence;
public DomainEventSubscription Subscribe(IReadOnlySet<string> channels)
{
var channel = Channel.CreateUnbounded<DomainEventDto>();
channel.Writer.TryComplete();
return new DomainEventSubscription(
channel.Reader,
CurrentSequence,
() => ValueTask.CompletedTask);
}
}
}