Forge
csharp3407750f
1using System.Net.Http.Json;
2using System.Text.Json;
3
4namespace AgentForge.Tests;
5
6public sealed class ForgeMrIopTests(ForgeWebApplicationFactory factory) : IClassFixture<ForgeWebApplicationFactory>
7{
8 private readonly HttpClient _client = factory.CreateClient();
9 private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true };
10
11 [Fact]
12 public async Task Merge_request_comment_requires_author()
13 {
14 await _client.PostAsJsonAsync("/api/v1/repos", new { name = "mr-iop" });
15 factory.Git.CreateCommitOnBranch("mr-iop", "main", "README.md", "m", "main");
16 factory.Git.CreateCommitOnBranch("mr-iop", "feat", "a.txt", "a", "feat");
17 await _client.PostAsJsonAsync("/api/v1/repos/mr-iop/merge-requests", new
18 {
19 title = "t",
20 sourceBranch = "feat",
21 });
22
23 var missing = await _client.PostAsJsonAsync("/api/v1/repos/mr-iop/merge-requests/1/comments", new
24 {
25 body = "review?",
26 });
27 Assert.Equal(System.Net.HttpStatusCode.BadRequest, missing.StatusCode);
28 }
29
30 [Fact]
31 public async Task Merge_request_comment_appears_in_detail_and_view()
32 {
33 await _client.PostAsJsonAsync("/api/v1/repos", new { name = "mr-thread" });
34 factory.Git.CreateCommitOnBranch("mr-thread", "main", "README.md", "m", "main");
35 factory.Git.CreateCommitOnBranch("mr-thread", "feat", "a.txt", "a", "feat");
36 await _client.PostAsJsonAsync("/api/v1/repos/mr-thread/merge-requests", new
37 {
38 title = "peer review",
39 sourceBranch = "feat",
40 });
41
42 using var commentRequest = new HttpRequestMessage(HttpMethod.Post, "/api/v1/repos/mr-thread/merge-requests/1/comments")
43 {
44 Content = JsonContent.Create(new { body = "looks good on Lens", author = "oar:Peer" }),
45 };
46 (await _client.SendAsync(commentRequest)).EnsureSuccessStatusCode();
47
48 var detail = await _client.GetFromJsonAsync<JsonElement>(
49 "/api/v1/repos/mr-thread/merge-requests/1",
50 JsonOptions);
51 Assert.Equal(1, detail.GetProperty("comments").GetArrayLength());
52 Assert.Equal("oar:Peer", detail.GetProperty("comments")[0].GetProperty("author").GetString());
53
54 var view = await _client.GetAsync("/view/repos/mr-thread/merge-requests/1");
55 view.EnsureSuccessStatusCode();
56 var html = await view.Content.ReadAsStringAsync();
57 Assert.Contains("oar:Peer", html, StringComparison.Ordinal);
58 Assert.Contains("looks good on Lens", html, StringComparison.Ordinal);
59 }
60
61 [Fact]
62 public async Task Handoff_mr_creates_upstream_in_human_driven_repo()
63 {
64 await _client.PostAsJsonAsync("/api/v1/repos", new
65 {
66 name = "sandbox",
67 driveMode = "agent-driven",
68 });
69 await _client.PostAsJsonAsync("/api/v1/repos", new
70 {
71 name = "canonical",
72 driveMode = "human-driven",
73 });
74
75 factory.Git.CreateCommitOnBranch("sandbox", "main", "README.md", "m", "main");
76 factory.Git.CreateCommitOnBranch("sandbox", "feat/up", "src/X.cs", "x", "feat");
77 factory.Git.CreateCommitOnBranch("canonical", "main", "README.md", "m", "main");
78 factory.Git.CreateCommitOnBranch("canonical", "feat/up", "src/X.cs", "x", "feat");
79
80 await _client.PostAsJsonAsync("/api/v1/repos/sandbox/merge-requests", new
81 {
82 title = "agent work",
83 sourceBranch = "feat/up",
84 });
85
86 var handoff = await _client.PostAsJsonAsync("/api/v1/repos/sandbox/merge-requests/1/handoff", new
87 {
88 targetRepo = "canonical",
89 });
90 handoff.EnsureSuccessStatusCode();
91 var upstream = await handoff.Content.ReadFromJsonAsync<JsonElement>(JsonOptions);
92 Assert.Equal(1, upstream.GetProperty("number").GetInt32());
93 Assert.Contains("sandbox!1", upstream.GetProperty("title").GetString(), StringComparison.Ordinal);
94
95 var list = await _client.GetFromJsonAsync<JsonElement>("/api/v1/repos/canonical/merge-requests", JsonOptions);
96 Assert.Equal(1, list.GetProperty("total").GetInt32());
97 }
98
99 [Fact]
100 public async Task Handoff_rejects_human_driven_source_repo()
101 {
102 await _client.PostAsJsonAsync("/api/v1/repos", new { name = "human-src", driveMode = "human-driven" });
103 await _client.PostAsJsonAsync("/api/v1/repos", new { name = "human-dst", driveMode = "human-driven" });
104 factory.Git.CreateCommitOnBranch("human-src", "main", "README.md", "m", "main");
105 factory.Git.CreateCommitOnBranch("human-src", "feat", "a.txt", "a", "feat");
106 factory.Git.CreateCommitOnBranch("human-dst", "main", "README.md", "m", "main");
107 factory.Git.CreateCommitOnBranch("human-dst", "feat", "a.txt", "a", "feat");
108 await _client.PostAsJsonAsync("/api/v1/repos/human-src/merge-requests", new
109 {
110 title = "human mr",
111 sourceBranch = "feat",
112 });
113
114 var response = await _client.PostAsJsonAsync("/api/v1/repos/human-src/merge-requests/1/handoff", new
115 {
116 targetRepo = "human-dst",
117 });
118 Assert.Equal(System.Net.HttpStatusCode.BadRequest, response.StatusCode);
119 }
120
121 [Fact]
122 public async Task Create_issue_with_actor_header_sets_created_by()
123 {
124 await _client.PostAsJsonAsync("/api/v1/repos", new { name = "actor-write" });
125 using var request = new HttpRequestMessage(HttpMethod.Post, "/api/v1/repos/actor-write/issues")
126 {
127 Content = JsonContent.Create(new { title = "intent" }),
128 };
129 request.Headers.Add("X-Forge-Actor", "oar:Comet");
130 (await _client.SendAsync(request)).EnsureSuccessStatusCode();
131
132 var issue = await _client.GetFromJsonAsync<JsonElement>("/api/v1/repos/actor-write/issues/1", JsonOptions);
133 Assert.Equal("oar:Comet", issue.GetProperty("createdBy").GetString());
134 }
135}
136
View only · write via MCP/CIDE