using System.Net; using System.Net.Http.Headers; using System.Net.Http.Json; using Xunit; namespace TeamUp.IntegrationTests; /// /// M3 acceptance: an owner adds a BYOK config, then configures an AI seat ("Aria", gated autonomy, /// a skill, that config) — flipping the seat to AI — without the key ever being exposed. /// public sealed class SeatConfigTests(PostgresFixture postgres) : IClassFixture { private sealed record BootstrapResponse(string Token, Guid MemberId, Guid OrganizationId); private sealed record OrganizationResponse(Guid Id, string Name); private sealed record TeamResponse(Guid Id, Guid OrganizationId, string Name); private sealed record ApiConfigDto(Guid Id, string Name, string Provider, string Model, string? Endpoint); private sealed record SeatResponse(Guid Id, Guid TeamId, string RoleName, string State, Guid? MemberId, Guid? AgentId); private sealed record AgentResponse( Guid Id, Guid SeatId, string Name, string? Monogram, string Autonomy, Guid ApiConfigId, Guid? FallbackApiConfigId, List SkillKeys, List Docs); [Fact] public async Task Owner_configures_an_ai_seat_with_skills_autonomy_and_byok_config() { await using var factory = new TeamUpWebFactory(postgres.ConnectionString); using var anon = factory.CreateClient(); var owner = await Bootstrap(anon); using var client = Authed(factory, owner.Token); await PostOk(client, "/api/orgboard/organizations", new { organizationId = owner.OrganizationId, name = "AliaSaaS" }); var team = await PostOk(client, "/api/orgboard/teams", new { organizationId = owner.OrganizationId, name = "IPNOPS" }); var config = await PostOk(client, "/api/integrations/api-configs", new { organizationId = owner.OrganizationId, name = "Vertex-Pro", provider = "stub", model = "gemini-pro", apiKey = "sk-byok-secret", }); // Create an open seat, then configure an AI agent on it. var seat = await PostOk(client, "/api/orgboard/seats", new { teamId = team.Id, roleName = "Product Owner" }); Assert.Equal("Open", seat.State); var agent = await PostOk(client, $"/api/orgboard/seats/{seat.Id}/agent", new { name = "Aria", monogram = "AR", autonomy = "Gated", apiConfigId = config.Id, skillKeys = new[] { "spec-writing", "story-breakdown" }, docs = new[] { "product-docs" }, }); Assert.Equal("Aria", agent.Name); Assert.Equal("Gated", agent.Autonomy); Assert.Equal(config.Id, agent.ApiConfigId); Assert.Contains("spec-writing", agent.SkillKeys); // Reading it back returns the same configuration. var fetched = await client.GetFromJsonAsync($"/api/orgboard/seats/{seat.Id}/agent"); Assert.Equal(agent.Id, fetched!.Id); // The seat is now an AI seat pointing at the agent. var seats = await client.GetFromJsonAsync>($"/api/orgboard/seats?teamId={team.Id}"); var aiSeat = seats!.Single(s => s.Id == seat.Id); Assert.Equal("Ai", aiSeat.State); Assert.Equal(agent.Id, aiSeat.AgentId); } private static async Task Bootstrap(HttpClient client) { var response = await client.PostAsJsonAsync("/api/identity/bootstrap", new { organizationName = "AliaSaaS", ownerEmail = "owner@alia.test", ownerDisplayName = "Owner", ownerPassword = "Passw0rd!", }); var owner = await response.Content.ReadFromJsonAsync(); Assert.NotNull(owner); return owner!; } private static HttpClient Authed(TeamUpWebFactory factory, string token) { var client = factory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); return client; } private static async Task PostOk(HttpClient client, string url, object body) { var response = await client.PostAsJsonAsync(url, body); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var value = await response.Content.ReadFromJsonAsync(); Assert.NotNull(value); return value!; } }