131 lines
5.3 KiB
C#
131 lines
5.3 KiB
C#
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.AspNetCore.Mvc.Testing;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using TermRemoteCtl.Agent.History;
|
|
using TermRemoteCtl.Agent.Sessions;
|
|
|
|
namespace TermRemoteCtl.Agent.IntegrationTests;
|
|
|
|
public sealed class SessionHistoryApiTests
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
|
|
|
[Fact]
|
|
public async Task GetHistory_Returns_Recent_Lines_And_HasMoreAbove()
|
|
{
|
|
await using var fixture = new AgentFixture();
|
|
using var client = fixture.CreateClient();
|
|
|
|
var registry = fixture.Services.GetRequiredService<SessionRegistry>();
|
|
var session = registry.Create("codex-main", DateTimeOffset.UtcNow);
|
|
await registry.AppendOutputAsync(session.SessionId, "one\ntwo\nthree\n", CancellationToken.None);
|
|
|
|
var response = await client.GetFromJsonAsync<SessionHistoryResponse>(
|
|
$"/api/sessions/{session.SessionId}/history?lineCount=2",
|
|
JsonOptions);
|
|
|
|
Assert.NotNull(response);
|
|
Assert.Equal(session.SessionId, response!.SessionId);
|
|
Assert.Equal(["two", "three"], response.Lines);
|
|
Assert.True(response.HasMoreAbove);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetJournal_Returns_Persistent_Ordered_Items_With_Input_And_Output()
|
|
{
|
|
await using var fixture = new AgentFixture();
|
|
using var client = fixture.CreateClient();
|
|
|
|
var registry = fixture.Services.GetRequiredService<SessionRegistry>();
|
|
var session = registry.Create("codex-main", DateTimeOffset.UtcNow);
|
|
await registry.RecordAttachAsync(session.SessionId, CancellationToken.None);
|
|
await registry.RecordInputAsync(session.SessionId, "git status\r", CancellationToken.None);
|
|
await registry.RecordOutputAsync(session.SessionId, "PS> git status\r\n", CancellationToken.None);
|
|
|
|
var response = await client.GetFromJsonAsync<SessionJournalResponse>(
|
|
$"/api/sessions/{session.SessionId}/journal?limit=10",
|
|
JsonOptions);
|
|
|
|
Assert.NotNull(response);
|
|
Assert.Equal(session.SessionId, response!.SessionId);
|
|
Assert.Equal(["attach", "input", "output"], response.Items.Select(item => item.Kind).ToArray());
|
|
Assert.Equal([1L, 2L, 3L], response.Items.Select(item => item.Sequence).ToArray());
|
|
Assert.Equal(3L, response.CurrentSequence);
|
|
Assert.False(response.HasMoreBefore);
|
|
Assert.False(response.HasMoreAfter);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetJournal_Can_Page_Older_Items_With_BeforeSequence()
|
|
{
|
|
await using var fixture = new AgentFixture();
|
|
using var client = fixture.CreateClient();
|
|
|
|
var registry = fixture.Services.GetRequiredService<SessionRegistry>();
|
|
var session = registry.Create("codex-main", DateTimeOffset.UtcNow);
|
|
await registry.RecordInputAsync(session.SessionId, "first", CancellationToken.None);
|
|
await registry.RecordOutputAsync(session.SessionId, "first\r\n", CancellationToken.None);
|
|
await registry.RecordInputAsync(session.SessionId, "second", CancellationToken.None);
|
|
await registry.RecordOutputAsync(session.SessionId, "second\r\n", CancellationToken.None);
|
|
|
|
var response = await client.GetFromJsonAsync<SessionJournalResponse>(
|
|
$"/api/sessions/{session.SessionId}/journal?beforeSeq=4&limit=2",
|
|
JsonOptions);
|
|
|
|
Assert.NotNull(response);
|
|
Assert.Equal([2L, 3L], response!.Items.Select(item => item.Sequence).ToArray());
|
|
Assert.True(response.HasMoreBefore);
|
|
Assert.True(response.HasMoreAfter);
|
|
Assert.Equal(4L, response.CurrentSequence);
|
|
}
|
|
|
|
private sealed class AgentFixture : WebApplicationFactory<Program>
|
|
{
|
|
private readonly string _dataRoot = Path.Combine(Path.GetTempPath(), "TermRemoteCtl.Tests", Guid.NewGuid().ToString("N"));
|
|
|
|
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
|
{
|
|
builder.UseEnvironment("Development");
|
|
builder.ConfigureAppConfiguration((_, configBuilder) =>
|
|
{
|
|
configBuilder.AddInMemoryCollection(new Dictionary<string, string?>
|
|
{
|
|
["Agent:DataRoot"] = _dataRoot,
|
|
["Agent:BindAddress"] = "127.0.0.1",
|
|
["Agent:HttpsPort"] = "9443",
|
|
["Agent:WebSocketFrameFlushMilliseconds"] = "33",
|
|
["Agent:RingBufferLineLimit"] = "4000"
|
|
});
|
|
});
|
|
}
|
|
|
|
public new async ValueTask DisposeAsync()
|
|
{
|
|
await base.DisposeAsync();
|
|
if (Directory.Exists(_dataRoot))
|
|
{
|
|
Directory.Delete(_dataRoot, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private sealed record SessionHistoryResponse(string SessionId, IReadOnlyList<string> Lines, bool HasMoreAbove);
|
|
|
|
private sealed record SessionJournalResponse(
|
|
string SessionId,
|
|
IReadOnlyList<SessionJournalItemResponse> Items,
|
|
bool HasMoreBefore,
|
|
bool HasMoreAfter,
|
|
long CurrentSequence);
|
|
|
|
private sealed record SessionJournalItemResponse(
|
|
string SessionId,
|
|
long Sequence,
|
|
string Kind,
|
|
string Payload,
|
|
DateTimeOffset TimestampUtc);
|
|
}
|