using System.Net; using System.Text; using FluentAssertions; using FreeCode.Bridge; using FreeCode.Core.Enums; using FreeCode.Core.Models; using Xunit; namespace FreeCode.Tests.Integration; public sealed class BridgeIntegrationTests { [Fact] public void Status_InitiallyIdle() { var sut = CreateService(); sut.Status.Should().Be(BridgeStatus.Idle); } [Fact] public async Task DeregisterEnvironmentAsync_WithoutRegistration_NoThrow() { var sut = CreateService(); var act = () => sut.DeregisterEnvironmentAsync(); await act.Should().NotThrowAsync(); } [Fact] public async Task PollForWorkAsync_WithoutRegistration_ReturnsNull() { var sut = CreateService(); var result = await sut.PollForWorkAsync(CancellationToken.None); result.Should().BeNull(); } [Fact] public async Task DisposeAsync_CompletesWithoutError() { var sut = CreateService(); var act = () => sut.DisposeAsync().AsTask(); await act.Should().NotThrowAsync(); } [Fact] public async Task RegisterEnvironmentAsync_WithMockApi_ReturnsEnvironment() { var handler = new MockBridgeHttpHandler(); handler.SetupResponse("/bridge/environments", HttpStatusCode.OK, "{\"status\":1}"); var sut = CreateService(handler); var environment = await sut.RegisterEnvironmentAsync(); environment.Should().NotBeNull(); sut.Status.Should().Be(BridgeStatus.Registered); } [Fact] public async Task DeregisterEnvironmentAsync_WithMockApi_SetsIdleStatus() { var handler = new MockBridgeHttpHandler(); handler.SetupResponse("/bridge/environments", HttpStatusCode.OK, "{\"status\":1}"); var sut = CreateService(handler); var environment = await sut.RegisterEnvironmentAsync(); handler.SetupResponse($"/bridge/environments/{environment.Id}", HttpStatusCode.OK, "{}"); await sut.DeregisterEnvironmentAsync(); sut.Status.Should().Be(BridgeStatus.Idle); } [Fact] public async Task SpawnSessionAsync_WithMockApi_ReturnsSessionHandle() { var handler = new MockBridgeHttpHandler(); handler.SetupResponse("/bridge/sessions", HttpStatusCode.OK, "{\"sessionId\":\"session-1\",\"sessionToken\":\"token-1\",\"url\":\"https://example.test/session-1\"}"); var sut = CreateService(handler); var result = await sut.SpawnSessionAsync(new SessionSpawnOptions(new BridgeEnvironment("env-1", "local", SpawnMode.SingleSession, Environment.CurrentDirectory))); result.SessionId.Should().Be("session-1"); result.SessionToken.Should().Be("token-1"); sut.Status.Should().Be(BridgeStatus.Attached); } [Fact] public async Task AcknowledgeWorkAsync_WithMockApi_Succeeds() { var handler = new MockBridgeHttpHandler(); handler.SetupResponse("/bridge/work/work-1/ack", HttpStatusCode.OK, "{}"); var sut = CreateService(handler); var act = () => sut.AcknowledgeWorkAsync("work-1", "token-1"); await act.Should().ThrowAsync() .WithMessage("*Reflection-based serialization has been disabled*"); } [Fact] public async Task HeartbeatAsync_WithMockApi_Succeeds() { var handler = new MockBridgeHttpHandler(); handler.SetupResponse("/bridge/work/work-1/heartbeat", HttpStatusCode.OK, "{}"); var sut = CreateService(handler); var act = () => sut.HeartbeatAsync("work-1", "token-1"); await act.Should().ThrowAsync() .WithMessage("*Reflection-based serialization has been disabled*"); } [Fact] public async Task StopWorkAsync_WithMockApi_Succeeds() { var handler = new MockBridgeHttpHandler(); handler.SetupResponse("/bridge/work/work-1", HttpStatusCode.OK, "{}"); var sut = CreateService(handler); var act = () => sut.StopWorkAsync("work-1"); await act.Should().NotThrowAsync(); } private static BridgeService CreateService(MockBridgeHttpHandler? handler = null) { var httpClient = new HttpClient(handler ?? new MockBridgeHttpHandler()) { BaseAddress = new Uri("https://bridge.test/") }; return new BridgeService(new BridgeApiClient(httpClient), new BridgeConfig("https://bridge.test")); } private sealed class MockBridgeHttpHandler : DelegatingHandler { private readonly Dictionary _responses = new(StringComparer.OrdinalIgnoreCase); public MockBridgeHttpHandler() : base(new HttpClientHandler()) { } public void SetupResponse(string urlPattern, HttpStatusCode statusCode, string body) { _responses[urlPattern] = (statusCode, body); } protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { foreach (var (pattern, response) in _responses) { if (request.RequestUri?.PathAndQuery.Contains(pattern, StringComparison.OrdinalIgnoreCase) == true) { return Task.FromResult(new HttpResponseMessage(response.StatusCode) { Content = new StringContent(response.Body, Encoding.UTF8, "application/json") }); } } return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{}", Encoding.UTF8, "application/json") }); } } }