165 lines
6.4 KiB
C#
165 lines
6.4 KiB
C#
using System.Net;
|
|
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.Sessions;
|
|
|
|
namespace TermRemoteCtl.Agent.IntegrationTests;
|
|
|
|
public sealed class ProjectApiTests
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
|
|
|
[Fact]
|
|
public async Task Create_List_Update_And_Detail_Project_Flow_Works()
|
|
{
|
|
await using var fixture = new AgentFixture();
|
|
using var client = fixture.CreateClient();
|
|
|
|
var createResponse = await client.PostAsJsonAsync(
|
|
"/api/projects",
|
|
new { name = "TermRemoteCtl", workingDirectory = fixture.ValidProjectPath });
|
|
createResponse.EnsureSuccessStatusCode();
|
|
|
|
var created = await createResponse.Content.ReadFromJsonAsync<ProjectResponse>(JsonOptions);
|
|
Assert.NotNull(created);
|
|
Assert.Equal("TermRemoteCtl", created!.Name);
|
|
Assert.Equal(fixture.ValidProjectPath, created.WorkingDirectory);
|
|
|
|
var listed = await client.GetFromJsonAsync<List<ProjectResponse>>("/api/projects", JsonOptions);
|
|
Assert.NotNull(listed);
|
|
Assert.Contains(listed!, project => project.ProjectId == created.ProjectId);
|
|
|
|
var registry = fixture.Services.GetRequiredService<SessionRegistry>();
|
|
registry.Create(
|
|
"TermRemoteCtl",
|
|
DateTimeOffset.UtcNow,
|
|
projectId: created.ProjectId,
|
|
workingDirectory: created.WorkingDirectory);
|
|
|
|
var detail = await client.GetFromJsonAsync<ProjectDetailResponse>(
|
|
$"/api/projects/{created.ProjectId}",
|
|
JsonOptions);
|
|
Assert.NotNull(detail);
|
|
Assert.Equal(created.ProjectId, detail!.ProjectId);
|
|
Assert.Single(detail.RecentSessions);
|
|
|
|
var updateResponse = await client.PutAsJsonAsync(
|
|
$"/api/projects/{created.ProjectId}",
|
|
new { name = "TRC", workingDirectory = fixture.UpdatedProjectPath });
|
|
updateResponse.EnsureSuccessStatusCode();
|
|
|
|
var updated = await updateResponse.Content.ReadFromJsonAsync<ProjectResponse>(JsonOptions);
|
|
Assert.NotNull(updated);
|
|
Assert.Equal("TRC", updated!.Name);
|
|
Assert.Equal(fixture.UpdatedProjectPath, updated.WorkingDirectory);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Create_Project_Returns_BadRequest_For_Invalid_WorkingDirectory()
|
|
{
|
|
await using var fixture = new AgentFixture();
|
|
using var client = fixture.CreateClient();
|
|
|
|
var response = await client.PostAsJsonAsync(
|
|
"/api/projects",
|
|
new { name = "Broken", workingDirectory = "Z:\\path\\does-not-exist" });
|
|
|
|
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Delete_Project_Cascades_Associated_Sessions()
|
|
{
|
|
await using var fixture = new AgentFixture();
|
|
using var client = fixture.CreateClient();
|
|
|
|
var createProjectResponse = await client.PostAsJsonAsync(
|
|
"/api/projects",
|
|
new { name = "TermRemoteCtl", workingDirectory = fixture.ValidProjectPath });
|
|
createProjectResponse.EnsureSuccessStatusCode();
|
|
var project = await createProjectResponse.Content.ReadFromJsonAsync<ProjectResponse>(JsonOptions);
|
|
Assert.NotNull(project);
|
|
|
|
var createSessionResponse = await client.PostAsJsonAsync(
|
|
"/api/sessions",
|
|
new { projectId = project!.ProjectId });
|
|
createSessionResponse.EnsureSuccessStatusCode();
|
|
var session = await createSessionResponse.Content.ReadFromJsonAsync<SessionResponse>(JsonOptions);
|
|
Assert.NotNull(session);
|
|
|
|
var deleteResponse = await client.DeleteAsync($"/api/projects/{project.ProjectId}");
|
|
deleteResponse.EnsureSuccessStatusCode();
|
|
|
|
var listedProjects = await client.GetFromJsonAsync<List<ProjectResponse>>("/api/projects", JsonOptions);
|
|
Assert.NotNull(listedProjects);
|
|
Assert.DoesNotContain(listedProjects!, item => item.ProjectId == project.ProjectId);
|
|
|
|
var listedSessions = await client.GetFromJsonAsync<List<SessionResponse>>("/api/sessions", JsonOptions);
|
|
Assert.NotNull(listedSessions);
|
|
Assert.DoesNotContain(listedSessions!, item => item.SessionId == session!.SessionId);
|
|
}
|
|
|
|
private sealed class AgentFixture : WebApplicationFactory<Program>
|
|
{
|
|
private readonly string _dataRoot = Path.Combine(Path.GetTempPath(), "TermRemoteCtl.Tests", Guid.NewGuid().ToString("N"));
|
|
|
|
public string ValidProjectPath => Path.Combine(_dataRoot, "project-a");
|
|
|
|
public string UpdatedProjectPath => Path.Combine(_dataRoot, "project-b");
|
|
|
|
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
|
{
|
|
Directory.CreateDirectory(ValidProjectPath);
|
|
Directory.CreateDirectory(UpdatedProjectPath);
|
|
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 ProjectResponse(
|
|
string ProjectId,
|
|
string Name,
|
|
string WorkingDirectory,
|
|
DateTimeOffset CreatedAtUtc,
|
|
DateTimeOffset UpdatedAtUtc);
|
|
|
|
private sealed record ProjectDetailResponse(
|
|
string ProjectId,
|
|
string Name,
|
|
string WorkingDirectory,
|
|
IReadOnlyList<SessionSummaryResponse> RecentSessions);
|
|
|
|
private sealed record SessionSummaryResponse(string SessionId, string Name, string Status);
|
|
|
|
private sealed record SessionResponse(
|
|
string SessionId,
|
|
string Name,
|
|
string Status,
|
|
string? ProjectId,
|
|
DateTimeOffset CreatedAtUtc,
|
|
DateTimeOffset UpdatedAtUtc);
|
|
}
|