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!;
}
}