Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions PolyPilot.Tests/DevTunnelServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void NewService_StateIsNotStarted()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());

Assert.Equal(TunnelState.NotStarted, service.State);
Assert.Null(service.TunnelUrl);
Expand Down Expand Up @@ -179,7 +179,7 @@ public async Task TryExtractInfo_ExtractsUrlFromConnectLine()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());
var tcs = new TaskCompletionSource<bool>();

InvokeTryExtractInfo(service, "Connect via browser: https://my-tunnel.devtunnels.ms", tcs);
Expand All @@ -194,7 +194,7 @@ public async Task TryExtractInfo_ExtractsUrlFromGenericDevTunnelLine()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());
var tcs = new TaskCompletionSource<bool>();

InvokeTryExtractInfo(service, "Ready at https://abc.devtunnels.ms", tcs);
Expand All @@ -209,7 +209,7 @@ public void TryExtractInfo_ExtractsTunnelIdFromPrimaryPattern()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());
var tcs = new TaskCompletionSource<bool>();

InvokeTryExtractInfo(service, "Tunnel ID: my-cool-tunnel", tcs);
Expand All @@ -224,7 +224,7 @@ public void TryExtractInfo_ExtractsTunnelIdFromAltPattern()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());
var tcs = new TaskCompletionSource<bool>();

InvokeTryExtractInfo(service, "Hosting port for tunnel: alt-id-123", tcs);
Expand All @@ -237,7 +237,7 @@ public async Task TryExtractInfo_ExtractsBothIdAndUrl()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());
var tcs = new TaskCompletionSource<bool>();

// First line: tunnel ID
Expand All @@ -257,7 +257,7 @@ public void TryExtractInfo_IgnoresDuplicateUrls()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());
var tcs = new TaskCompletionSource<bool>();

InvokeTryExtractInfo(service, "Connect via browser: https://first.devtunnels.ms", tcs);
Expand All @@ -274,7 +274,7 @@ public void TryExtractInfo_IgnoresDuplicateTunnelIds()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());
var tcs = new TaskCompletionSource<bool>();

InvokeTryExtractInfo(service, "Tunnel ID: first-id", tcs);
Expand All @@ -289,7 +289,7 @@ public void TryExtractInfo_NoMatchDoesNothing()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());
var tcs = new TaskCompletionSource<bool>();

InvokeTryExtractInfo(service, "Just some random log output", tcs);
Expand All @@ -304,7 +304,7 @@ public void TryExtractInfo_TrimsTrailingSlashFromUrl()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());
var tcs = new TaskCompletionSource<bool>();

InvokeTryExtractInfo(service, "Connect via browser: https://trimtest.devtunnels.ms/", tcs);
Expand All @@ -319,7 +319,7 @@ public void Stop_ResetsAllState()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());

// Simulate some state via TryExtractInfo
var tcs = new TaskCompletionSource<bool>();
Expand All @@ -340,7 +340,7 @@ public void Stop_FiresOnStateChanged()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());

int stateChangedCount = 0;
service.OnStateChanged += () => stateChangedCount++;
Expand All @@ -358,7 +358,7 @@ public void Dispose_CallsStop()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());

int stateChangedCount = 0;
service.OnStateChanged += () => stateChangedCount++;
Expand Down Expand Up @@ -534,7 +534,7 @@ public void ParsesRealDevTunnelHostOutput_TunnelId()

var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());
var tcs = new TaskCompletionSource<bool>();

foreach (var line in lines)
Expand All @@ -550,7 +550,7 @@ public void ParsesRealDevTunnelHostOutput_WithPort()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());
var tcs = new TaskCompletionSource<bool>();

InvokeTryExtractInfo(service,
Expand Down Expand Up @@ -627,7 +627,7 @@ public async Task HostAsync_WhenAlreadyRunning_ReturnsTrueImmediately()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());

// Set state to Running via reflection
SetState(service, TunnelState.Running);
Expand All @@ -645,7 +645,7 @@ public void TryExtractInfo_PrefersConnectUrl_OverGenericUrl()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());
var tcs = new TaskCompletionSource<bool>();

// This line matches both ConnectUrlRegex and TunnelUrlRegex
Expand All @@ -663,7 +663,7 @@ public void TryExtractInfo_PrimaryTunnelIdTakesPriority()
{
var bridge = new WsBridgeServer();
var copilot = CreateTestCopilotService();
var service = new DevTunnelService(bridge, copilot);
var service = new DevTunnelService(bridge, copilot, new RepoManager());
var tcs = new TaskCompletionSource<bool>();

// This matches TunnelIdRegex directly
Expand Down
176 changes: 176 additions & 0 deletions PolyPilot.Tests/RemoteRepoTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
using System.Text.Json;
using PolyPilot.Models;

namespace PolyPilot.Tests;

public class RemoteRepoTests
{
[Fact]
public void BridgeMessageTypes_RepoConstants_Defined()
{
Assert.Equal("add_repo", BridgeMessageTypes.AddRepo);
Assert.Equal("remove_repo", BridgeMessageTypes.RemoveRepo);
Assert.Equal("list_repos", BridgeMessageTypes.ListRepos);
Assert.Equal("repos_list", BridgeMessageTypes.ReposList);
Assert.Equal("repo_added", BridgeMessageTypes.RepoAdded);
Assert.Equal("repo_progress", BridgeMessageTypes.RepoProgress);
Assert.Equal("repo_error", BridgeMessageTypes.RepoError);
}

[Fact]
public void AddRepoPayload_RoundTrip()
{
var payload = new AddRepoPayload { Url = "https://github.com/PureWeen/PolyPilot", RequestId = "abc123" };
var msg = BridgeMessage.Create(BridgeMessageTypes.AddRepo, payload);
var json = msg.Serialize();
var restored = BridgeMessage.Deserialize(json);

Assert.NotNull(restored);
Assert.Equal(BridgeMessageTypes.AddRepo, restored!.Type);

var p = restored.GetPayload<AddRepoPayload>();
Assert.NotNull(p);
Assert.Equal("https://github.com/PureWeen/PolyPilot", p!.Url);
Assert.Equal("abc123", p.RequestId);
}

[Fact]
public void RemoveRepoPayload_RoundTrip()
{
var payload = new RemoveRepoPayload { RepoId = "PureWeen-PolyPilot", DeleteFromDisk = true, GroupId = "group1" };
var msg = BridgeMessage.Create(BridgeMessageTypes.RemoveRepo, payload);
var json = msg.Serialize();
var restored = BridgeMessage.Deserialize(json);

var p = restored!.GetPayload<RemoveRepoPayload>();
Assert.NotNull(p);
Assert.Equal("PureWeen-PolyPilot", p!.RepoId);
Assert.True(p.DeleteFromDisk);
Assert.Equal("group1", p.GroupId);
}

[Fact]
public void ReposListPayload_RoundTrip()
{
var payload = new ReposListPayload
{
RequestId = "req1",
Repos = new()
{
new RepoSummary { Id = "PureWeen-PolyPilot", Name = "PolyPilot", Url = "https://github.com/PureWeen/PolyPilot" },
new RepoSummary { Id = "dotnet-maui", Name = "maui", Url = "https://github.com/dotnet/maui" }
}
};
var msg = BridgeMessage.Create(BridgeMessageTypes.ReposList, payload);
var json = msg.Serialize();
var restored = BridgeMessage.Deserialize(json);

var p = restored!.GetPayload<ReposListPayload>();
Assert.NotNull(p);
Assert.Equal(2, p!.Repos.Count);
Assert.Equal("PureWeen-PolyPilot", p.Repos[0].Id);
Assert.Equal("maui", p.Repos[1].Name);
}

[Fact]
public void RepoAddedPayload_RoundTrip()
{
var payload = new RepoAddedPayload
{
RequestId = "req1",
RepoId = "PureWeen-PolyPilot",
RepoName = "PolyPilot",
Url = "https://github.com/PureWeen/PolyPilot"
};
var msg = BridgeMessage.Create(BridgeMessageTypes.RepoAdded, payload);
var json = msg.Serialize();
var restored = BridgeMessage.Deserialize(json);

var p = restored!.GetPayload<RepoAddedPayload>();
Assert.NotNull(p);
Assert.Equal("req1", p!.RequestId);
Assert.Equal("PureWeen-PolyPilot", p.RepoId);
Assert.Equal("PolyPilot", p.RepoName);
}

[Fact]
public void RepoProgressPayload_RoundTrip()
{
var payload = new RepoProgressPayload { RequestId = "req1", Message = "Cloning 45%..." };
var msg = BridgeMessage.Create(BridgeMessageTypes.RepoProgress, payload);
var json = msg.Serialize();
var restored = BridgeMessage.Deserialize(json);

var p = restored!.GetPayload<RepoProgressPayload>();
Assert.NotNull(p);
Assert.Equal("req1", p!.RequestId);
Assert.Equal("Cloning 45%...", p.Message);
}

[Fact]
public void RepoErrorPayload_RoundTrip()
{
var payload = new RepoErrorPayload { RequestId = "req1", Error = "Authentication failed" };
var msg = BridgeMessage.Create(BridgeMessageTypes.RepoError, payload);
var json = msg.Serialize();
var restored = BridgeMessage.Deserialize(json);

var p = restored!.GetPayload<RepoErrorPayload>();
Assert.NotNull(p);
Assert.Equal("req1", p!.RequestId);
Assert.Equal("Authentication failed", p.Error);
}

[Fact]
public void ListReposPayload_RoundTrip()
{
var payload = new ListReposPayload { RequestId = "req1" };
var msg = BridgeMessage.Create(BridgeMessageTypes.ListRepos, payload);
var json = msg.Serialize();
var restored = BridgeMessage.Deserialize(json);

Assert.Equal(BridgeMessageTypes.ListRepos, restored!.Type);
var p = restored.GetPayload<ListReposPayload>();
Assert.NotNull(p);
Assert.Equal("req1", p!.RequestId);
}

[Fact]
public void RepoSummary_DefaultValues()
{
var summary = new RepoSummary();
Assert.Equal("", summary.Id);
Assert.Equal("", summary.Name);
Assert.Equal("", summary.Url);
}

[Fact]
public void StubBridgeClient_AddRepo_TracksCall()
{
var stub = new StubWsBridgeClient();
var result = stub.AddRepoAsync("https://github.com/dotnet/maui").Result;

Check warning on line 151 in PolyPilot.Tests/RemoteRepoTests.cs

View workflow job for this annotation

GitHub Actions / Build & Test

Test methods should not use blocking task operations, as they can cause deadlocks. Use an async test method and await instead. (https://xunit.net/xunit.analyzers/rules/xUnit1031)

Check warning on line 151 in PolyPilot.Tests/RemoteRepoTests.cs

View workflow job for this annotation

GitHub Actions / Build & Test

Test methods should not use blocking task operations, as they can cause deadlocks. Use an async test method and await instead. (https://xunit.net/xunit.analyzers/rules/xUnit1031)

Assert.Equal(1, stub.AddRepoCallCount);
Assert.Equal("https://github.com/dotnet/maui", stub.LastAddedRepoUrl);
Assert.Equal("maui", result.RepoId);
}

[Fact]
public void StubBridgeClient_RemoveRepo_TracksCall()
{
var stub = new StubWsBridgeClient();
stub.RemoveRepoAsync("dotnet-maui", true, "group1").Wait();

Check warning on line 162 in PolyPilot.Tests/RemoteRepoTests.cs

View workflow job for this annotation

GitHub Actions / Build & Test

Test methods should not use blocking task operations, as they can cause deadlocks. Use an async test method and await instead. (https://xunit.net/xunit.analyzers/rules/xUnit1031)

Check warning on line 162 in PolyPilot.Tests/RemoteRepoTests.cs

View workflow job for this annotation

GitHub Actions / Build & Test

Test methods should not use blocking task operations, as they can cause deadlocks. Use an async test method and await instead. (https://xunit.net/xunit.analyzers/rules/xUnit1031)

Assert.Equal(1, stub.RemoveRepoCallCount);
Assert.Equal("dotnet-maui", stub.LastRemovedRepoId);
}

[Fact]
public void StubBridgeClient_RequestRepos_TracksCall()
{
var stub = new StubWsBridgeClient();
stub.RequestReposAsync().Wait();

Check warning on line 172 in PolyPilot.Tests/RemoteRepoTests.cs

View workflow job for this annotation

GitHub Actions / Build & Test

Test methods should not use blocking task operations, as they can cause deadlocks. Use an async test method and await instead. (https://xunit.net/xunit.analyzers/rules/xUnit1031)

Check warning on line 172 in PolyPilot.Tests/RemoteRepoTests.cs

View workflow job for this annotation

GitHub Actions / Build & Test

Test methods should not use blocking task operations, as they can cause deadlocks. Use an async test method and await instead. (https://xunit.net/xunit.analyzers/rules/xUnit1031)

Assert.Equal(1, stub.RequestReposCallCount);
}
}
26 changes: 26 additions & 0 deletions PolyPilot.Tests/TestStubs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,32 @@ public Task RenameSessionAsync(string oldName, string newName, CancellationToken
}
public Task<DirectoriesListPayload> ListDirectoriesAsync(string? path = null, CancellationToken ct = default)
=> Task.FromResult(new DirectoriesListPayload());

// Repo operations
public event Action<ReposListPayload>? OnReposListReceived;
public string? LastAddedRepoUrl { get; private set; }
public int AddRepoCallCount { get; private set; }
public Task<RepoAddedPayload> AddRepoAsync(string url, Action<string>? onProgress = null, CancellationToken ct = default)
{
LastAddedRepoUrl = url;
AddRepoCallCount++;
var id = url.Split('/').Last();
return Task.FromResult(new RepoAddedPayload { RequestId = "test", RepoId = id, RepoName = id, Url = url });
}
public string? LastRemovedRepoId { get; private set; }
public int RemoveRepoCallCount { get; private set; }
public Task RemoveRepoAsync(string repoId, bool deleteFromDisk, string? groupId = null, CancellationToken ct = default)
{
LastRemovedRepoId = repoId;
RemoveRepoCallCount++;
return Task.CompletedTask;
}
public int RequestReposCallCount { get; private set; }
public Task RequestReposAsync(CancellationToken ct = default)
{
RequestReposCallCount++;
return Task.CompletedTask;
}
}

internal class StubDemoService : IDemoService
Expand Down
Loading
Loading