Bifrost/tests/Bifrost.Web.Tests/MemberConsoleTests.cs
2026-10-10 09:56:08 +02:00

198 lines
8.4 KiB
C#

using System.Net;
using Bifrost.Web.Configuration;
using Bifrost.Web.Features.ConceptVotes;
using Bifrost.Web.Features.Shared;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Bifrost.Web.Data;
using Bifrost.Web.Domain;
using Bifrost.Web.Tests.Support;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
namespace Bifrost.Web.Tests;
public sealed class MemberConsoleTests : IClassFixture<TestWebApplicationFactory>
{
private readonly TestWebApplicationFactory _factory;
public MemberConsoleTests(TestWebApplicationFactory factory)
{
_factory = factory;
}
[Fact]
public async Task Active_member_can_open_console()
{
using var client = _factory.CreateClient();
using var response = await client.GetAsync("/App");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var html = await response.Content.ReadAsStringAsync();
Assert.Contains("Operational picture", html);
}
[Fact]
public async Task Anonymous_user_is_redirected_to_sign_in()
{
using var client = _factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
client.DefaultRequestHeaders.Add("X-Test-Anonymous", "true");
using var response = await client.GetAsync("/App");
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
Assert.Contains("/auth/sign-in", response.Headers.Location?.OriginalString);
}
[Fact]
public async Task Member_console_shows_recent_bridge_receipts()
{
using (var warmupClient = _factory.CreateClient())
{
_ = await warmupClient.GetAsync("/App");
}
using (var scope = _factory.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<BifrostDbContext>();
var actor = dbContext.UserAccounts.Single(x => x.NormalizedGitHubLogin == "TEST-ADMIN");
dbContext.BridgeActions.Add(new BridgeAction
{
ActorKind = BridgeActorKind.Agent,
ActorName = "nibu",
ActorUserAccountId = actor.Id,
TargetSurface = BridgeTargetSurface.GitHub,
ActionKind = BridgeActionKind.GitHubDraftPullRequest,
Status = BridgeActionStatus.Completed,
TargetRepositoryFullName = "gamecult/bifrost",
TargetLocator = "pulls",
SourceKind = "bifrost_governance_topic",
SourceId = "topic_123",
AuthorityReference = "dispatch-approved",
PolicyDecision = "Authorized through Bifrost policy.",
Title = "Draft motion implementation PR",
Summary = "Open a draft PR for the approved topic.",
ReceiptUrl = "https://github.com/GameCult/Bifrost/pull/99",
ExternalReceiptId = "99",
ReceiptPayload = "{\"pr\":99}",
RequestedAtUtc = DateTimeOffset.UtcNow.AddMinutes(-5),
UpdatedAtUtc = DateTimeOffset.UtcNow.AddMinutes(-1),
CompletedAtUtc = DateTimeOffset.UtcNow.AddMinutes(-1)
});
dbContext.AgentDispatchRuns.Add(new AgentDispatchRun
{
RequestId = "req_dispatch_123",
TargetRepoName = "Bifrost",
TargetRepositoryFullName = "GameCult/Bifrost",
TargetAgentIdentity = "nibu",
LaunchMode = "app-server",
Status = AgentDispatchRunStatus.Completed,
StartedByUserAccountId = actor.Id,
WorkerProcessId = 4242,
ThreadId = "thread_1",
TurnId = "turn_1",
LogPath = "E:/Projects/Bifrost/.bifrost/agent-dispatch/req_dispatch_123/codex.log",
ResultPath = "E:/Projects/Bifrost/.bifrost/agent-dispatch/req_dispatch_123/result.json",
Note = "Codex app turn completed for thread_1/turn_1.",
StartedAtUtc = DateTimeOffset.UtcNow.AddMinutes(-8),
UpdatedAtUtc = DateTimeOffset.UtcNow.AddMinutes(-2),
CompletedAtUtc = DateTimeOffset.UtcNow.AddMinutes(-2)
});
dbContext.AgentTransportReceipts.Add(new AgentTransportReceipt
{
RequestId = "req_transport_123",
Title = "Queue the GitHub bridge hardening pass",
TargetRepoName = "Bifrost",
TargetRepositoryFullName = "GameCult/Bifrost",
TargetAgentIdentity = "nibu",
ActivityKind = AgentTransportReceiptKind.Claimed,
Status = "claimed",
ActorUserAccountId = actor.Id,
ActorName = "bifrost-dispatcher",
Note = "Request claimed for Bifrost.",
OccurredAtUtc = DateTimeOffset.UtcNow.AddMinutes(-3)
});
dbContext.GovernanceActivityReceipts.Add(new GovernanceActivityReceipt
{
TopicId = "topic_123",
CommentId = "comment_123",
DispatchRequestId = "req_transport_123",
Title = "Queue the GitHub bridge hardening pass",
JurisdictionRepoName = "Bifrost",
JurisdictionAgentIdentity = "nibu",
ActivityKind = GovernanceActivityReceiptKind.TopicPromoted,
ActorKind = "face",
ActorName = "nibu",
Note = "Governance topic promoted to update request req_transport_123.",
ActorUserAccountId = actor.Id,
OccurredAtUtc = DateTimeOffset.UtcNow.AddMinutes(-4)
});
dbContext.SaveChanges();
}
using var client = _factory.CreateClient();
using var response = await client.GetAsync("/App");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var html = await response.Content.ReadAsStringAsync();
Assert.Contains("Agent runs", html);
Assert.Contains("req_dispatch_123", html);
Assert.Contains("Transport receipts", html);
Assert.Contains("req_transport_123", html);
Assert.Contains("Governance receipts", html);
Assert.Contains("topic_123", html);
Assert.Contains("Bridge receipts", html);
Assert.Contains("Draft motion implementation PR", html);
Assert.Contains("nibu", html);
}
[Fact]
public async Task Member_console_activity_shows_a_purge_without_its_network_tag()
{
using (var warmupClient = _factory.CreateClient())
{
_ = await warmupClient.GetAsync("/App");
}
string tag;
using (var scope = _factory.Services.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<BifrostDbContext>();
var admin = await dbContext.UserAccounts
.Include(x => x.Membership)
.SingleAsync(x => x.NormalizedGitHubLogin == "TEST-ADMIN");
var now = DateTimeOffset.UtcNow;
dbContext.ConceptSubjects.Add(new ConceptSubject
{
Collection = "aetheria-concept-mill",
Key = "ship-a",
Title = "ship-a",
RegisteredAtUtc = now
});
await dbContext.SaveChangesAsync();
var service = scope.ServiceProvider.GetRequiredService<ConceptAppraisalService>();
var options = scope.ServiceProvider.GetRequiredService<IOptions<ConceptVoteOptions>>().Value;
var client = System.Net.IPAddress.Parse("203.0.113.7");
tag = NetworkPrefix.Tag(client, options.IPv4PrefixLength, options.IPv6PrefixLength, options.NetworkTagSecret);
var voter = VoterIdentity.For(CurrentBifrostActor.Anonymous, "member-view-device")!;
await service.CastAsync("aetheria-concept-mill", "ship-a", voter, ConceptStance.Up, client, default);
var removed = await service.PurgeAsync(
new CurrentBifrostActor(true, admin), tag, now.AddHours(-1), now.AddHours(1), default);
Assert.Equal(1, removed);
}
using var memberClient = _factory.CreateClient();
using var response = await memberClient.GetAsync("/App");
var html = await response.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Contains("Purge", html);
Assert.Contains("count", html);
Assert.DoesNotContain(tag, html);
}
}