| 1 | using System.Net; |
| 2 | using System.Net.Http.Json; |
| 3 | using System.Text.Json; |
| 4 | |
| 5 | namespace AgentForge.Tests; |
| 6 | |
| 7 | public sealed class ForgeCommandTests(ForgeWebApplicationFactory factory) : IClassFixture<ForgeWebApplicationFactory> |
| 8 | { |
| 9 | private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true }; |
| 10 | |
| 11 | [Fact] |
| 12 | public async Task Capabilities_includes_forge_commands() |
| 13 | { |
| 14 | var response = await factory.CreateClient().GetAsync("/api/v1/capabilities"); |
| 15 | response.EnsureSuccessStatusCode(); |
| 16 | |
| 17 | var caps = await response.Content.ReadFromJsonAsync<JsonElement>(JsonOptions); |
| 18 | var commands = caps.GetProperty("commands").EnumerateArray().ToList(); |
| 19 | Assert.NotEmpty(commands); |
| 20 | |
| 21 | var ids = commands.Select(c => c.GetProperty("commandId").GetString()).ToHashSet(StringComparer.Ordinal); |
| 22 | Assert.Contains("forge.issue.open", ids); |
| 23 | Assert.Contains("forge.issue.create", ids); |
| 24 | Assert.Contains("forge.merge_request.open", ids); |
| 25 | Assert.Contains("forge.repo.open", ids); |
| 26 | } |
| 27 | |
| 28 | [Fact] |
| 29 | public async Task Capabilities_surface_filter_limits_commands() |
| 30 | { |
| 31 | var response = await factory.CreateClient().GetAsync("/api/v1/capabilities?surface=mr-comment"); |
| 32 | response.EnsureSuccessStatusCode(); |
| 33 | |
| 34 | var caps = await response.Content.ReadFromJsonAsync<JsonElement>(JsonOptions); |
| 35 | var commands = caps.GetProperty("commands").EnumerateArray().ToList(); |
| 36 | Assert.All(commands, c => |
| 37 | { |
| 38 | var surfaces = c.GetProperty("surfaces").EnumerateArray().Select(e => e.GetString()).ToList(); |
| 39 | Assert.Contains("mr-comment", surfaces); |
| 40 | }); |
| 41 | } |
| 42 | |
| 43 | [Fact] |
| 44 | public async Task Execute_issue_open_redirects_to_view() |
| 45 | { |
| 46 | var client = factory.CreateClient(new() { AllowAutoRedirect = false }); |
| 47 | var payload = new |
| 48 | { |
| 49 | path = "/issue open", |
| 50 | args = "42", |
| 51 | context = new Dictionary<string, string> { ["repo"] = "demo" }, |
| 52 | }; |
| 53 | |
| 54 | var response = await client.PostAsJsonAsync("/api/v1/commands/execute", payload); |
| 55 | Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); |
| 56 | Assert.Equal("/view/repos/demo/issues/42", response.Headers.Location?.OriginalString); |
| 57 | } |
| 58 | |
| 59 | [Fact] |
| 60 | public async Task Execute_repo_open_redirects_to_view() |
| 61 | { |
| 62 | var client = factory.CreateClient(new() { AllowAutoRedirect = false }); |
| 63 | var payload = new |
| 64 | { |
| 65 | path = "/repo open", |
| 66 | context = new Dictionary<string, string> { ["repo"] = "demo" }, |
| 67 | }; |
| 68 | |
| 69 | var response = await client.PostAsJsonAsync("/api/v1/commands/execute", payload); |
| 70 | Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); |
| 71 | Assert.Equal("/view/repos/demo", response.Headers.Location?.OriginalString); |
| 72 | } |
| 73 | |
| 74 | [Fact] |
| 75 | public async Task Execute_unknown_path_returns_bad_request() |
| 76 | { |
| 77 | var response = await factory.CreateClient().PostAsJsonAsync( |
| 78 | "/api/v1/commands/execute", |
| 79 | new { path = "/not-a-command" }); |
| 80 | |
| 81 | Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); |
| 82 | } |
| 83 | } |
| 84 | |