Forge
csharp3407750f
1using System.Net;
2using System.Net.Http.Json;
3using System.Text.Json;
4using AgentForge.Data;
5using AgentForge.Models;
6using AgentForge.Services;
7using Microsoft.Extensions.DependencyInjection;
8
9namespace AgentForge.Tests;
10
11[Collection("ForgeEnvironment")]
12public sealed class ForgeOrgCatalogAdminAuthTests
13{
14 private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true };
15
16 [Fact]
17 public async Task Org_admin_with_login_token_can_create_repo_group_without_bootstrap()
18 {
19 Environment.SetEnvironmentVariable("FORGE_REQUIRE_AUTH", "true");
20 Environment.SetEnvironmentVariable("FORGE_BOOTSTRAP_TOKEN", "bootstrap-catalog-admin");
21 using var factory = new ForgeWebApplicationFactory { PluginBundle = "ams-showcase" };
22
23 using (var bootstrap = factory.CreateClient())
24 {
25 bootstrap.DefaultRequestHeaders.Authorization =
26 new("Bearer", "bootstrap-catalog-admin");
27
28 var createOrg = await bootstrap.PostAsJsonAsync(
29 "/api/v1/orgs",
30 new { slug = "catalog-admin-org", displayName = "Catalog Admin Org" },
31 JsonOptions);
32 createOrg.EnsureSuccessStatusCode();
33 }
34
35 var (adminToken, _) = SeedOrgAdmin(factory, "catalog-admin-org", "lead-dev");
36
37 using var adminClient = factory.CreateClient();
38 adminClient.DefaultRequestHeaders.Authorization = new("Bearer", adminToken);
39
40 var createGroup = await adminClient.PostAsJsonAsync(
41 "/api/v1/orgs/catalog-admin-org/repo-groups",
42 new { slug = "conferences", displayName = "Conferences" },
43 JsonOptions);
44 createGroup.EnsureSuccessStatusCode();
45
46 var body = await createGroup.Content.ReadFromJsonAsync<JsonElement>(JsonOptions);
47 Assert.Equal("conferences", body.GetProperty("slug").GetString());
48
49 Environment.SetEnvironmentVariable("FORGE_REQUIRE_AUTH", null);
50 Environment.SetEnvironmentVariable("FORGE_BOOTSTRAP_TOKEN", null);
51 }
52
53 [Fact]
54 public async Task Org_member_without_admin_role_cannot_create_repo_group()
55 {
56 Environment.SetEnvironmentVariable("FORGE_REQUIRE_AUTH", "true");
57 Environment.SetEnvironmentVariable("FORGE_BOOTSTRAP_TOKEN", "bootstrap-catalog-member");
58 using var factory = new ForgeWebApplicationFactory { PluginBundle = "ams-showcase" };
59
60 using (var bootstrap = factory.CreateClient())
61 {
62 bootstrap.DefaultRequestHeaders.Authorization =
63 new("Bearer", "bootstrap-catalog-member");
64
65 (await bootstrap.PostAsJsonAsync(
66 "/api/v1/orgs",
67 new { slug = "member-org", displayName = "Member Org" },
68 JsonOptions)).EnsureSuccessStatusCode();
69 }
70
71 var (memberToken, _) = SeedOrgAdmin(factory, "member-org", "regular-dev", ForgeOrgRole.Member);
72
73 using var memberClient = factory.CreateClient();
74 memberClient.DefaultRequestHeaders.Authorization = new("Bearer", memberToken);
75
76 var createGroup = await memberClient.PostAsJsonAsync(
77 "/api/v1/orgs/member-org/repo-groups",
78 new { slug = "blocked", displayName = "Blocked" },
79 JsonOptions);
80 Assert.Equal(HttpStatusCode.Forbidden, createGroup.StatusCode);
81
82 Environment.SetEnvironmentVariable("FORGE_REQUIRE_AUTH", null);
83 Environment.SetEnvironmentVariable("FORGE_BOOTSTRAP_TOKEN", null);
84 }
85
86 [Fact]
87 public void Owner_role_slug_parses_as_admin()
88 {
89 Assert.True(ForgeOrgRoleExtensions.TryParseSlug("owner", out var role));
90 Assert.True(role.IsAdmin());
91 }
92
93 private static (string Token, string Login) SeedOrgAdmin(
94 ForgeWebApplicationFactory factory,
95 string orgSlug,
96 string login,
97 ForgeOrgRole role = ForgeOrgRole.Admin)
98 {
99 using var scope = factory.Services.CreateScope();
100 var repository = scope.ServiceProvider.GetRequiredService<ForgeRepository>();
101 var auth = scope.ServiceProvider.GetRequiredService<ForgeAuthService>();
102 var org = repository.GetOrgBySlug(orgSlug)
103 ?? throw new InvalidOperationException($"Org '{orgSlug}' not found.");
104
105 var identity = repository.UpsertIdentity("github", $"sub-{login}", login, login);
106 if (!repository.IsOrgMember(org.Id, identity.Id))
107 repository.InsertOrgMember(org.Id, identity.Id, role);
108
109 var (_, plain) = auth.CreateToken($"oauth:github:{login}", "read,write,accept_merge");
110 return (plain, login);
111 }
112}
113
View only · write via MCP/CIDE