d9f9349117
SharedKernel contracts (so Assembler stays decoupled): IAgentRunContextProvider (agent + task) and ISkillCatalog (skill prompts by key). Implemented by OrgBoard (AgentRunContextProvider) and Skills (SkillCatalog). Assembler: - PromptAssembler builds house-style + identity + the agent's skill bodies + the task, and derives the primary action + risk from the agent's first skill. RAG/working-memory join at M6. - AgentRunExecutor (real): resolve context + skills → assemble → resolve BYOK config (with fallback) → call IModelClient → parse into action + risk → capture all on the AgentRun. Verified: build green; ArchitectureTests 8/8; IntegrationTests 29/29 — incl. the M4 acceptance: assigning a Spec task to Aria (PO, gated, stub BYOK) yields a Completed run with the assembled prompt (skill body + task title), action "write-spec", risk "Draft", and model output. Nothing executes — the gate is M5. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
77 lines
3.2 KiB
C#
77 lines
3.2 KiB
C#
using System.Net;
|
|
using System.Net.Http.Headers;
|
|
using System.Net.Http.Json;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using TeamUp.Modules.Assembler.Queue;
|
|
using TeamUp.Modules.Assembler.Runtime;
|
|
using Xunit;
|
|
|
|
namespace TeamUp.IntegrationTests;
|
|
|
|
/// <summary>
|
|
/// M4 Increment 1: a run is enqueued (web), claimed via FOR UPDATE SKIP LOCKED + processed
|
|
/// (worker services), and the AgentRun lifecycle reaches Completed.
|
|
/// </summary>
|
|
public sealed class AgentRunQueueTests(PostgresFixture postgres) : IClassFixture<PostgresFixture>
|
|
{
|
|
private sealed record BootstrapResponse(string Token, Guid MemberId, Guid OrganizationId);
|
|
|
|
private sealed record RunResponse(
|
|
Guid Id, Guid SeatId, Guid WorkItemId, Guid? AgentId, string Status,
|
|
string? ActionType, string? ActionRisk, string? Prompt, string? Output, string? Error);
|
|
|
|
[Fact]
|
|
public async Task Run_is_enqueued_claimed_and_processed()
|
|
{
|
|
await using var factory = new TeamUpWebFactory(postgres.ConnectionString);
|
|
using var anon = factory.CreateClient();
|
|
|
|
var bootstrap = await anon.PostAsJsonAsync("/api/identity/bootstrap", new
|
|
{
|
|
organizationName = "AliaSaaS",
|
|
ownerEmail = "owner@alia.test",
|
|
ownerDisplayName = "Owner",
|
|
ownerPassword = "Passw0rd!",
|
|
});
|
|
var owner = await bootstrap.Content.ReadFromJsonAsync<BootstrapResponse>();
|
|
Assert.NotNull(owner);
|
|
|
|
using var client = factory.CreateClient();
|
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", owner!.Token);
|
|
|
|
// Enqueue a run (web).
|
|
var created = await client.PostAsJsonAsync("/api/assembler/runs", new
|
|
{
|
|
seatId = Guid.NewGuid(),
|
|
workItemId = Guid.NewGuid(),
|
|
});
|
|
Assert.Equal(HttpStatusCode.OK, created.StatusCode);
|
|
var run = await created.Content.ReadFromJsonAsync<RunResponse>();
|
|
Assert.Equal("Queued", run!.Status);
|
|
|
|
// Drain one job exactly as the worker does: claim (SKIP LOCKED) then process.
|
|
await using (var scope = factory.Services.CreateAsyncScope())
|
|
{
|
|
var queue = scope.ServiceProvider.GetRequiredService<JobQueue>();
|
|
var job = await queue.ClaimNextAsync("test-worker");
|
|
Assert.NotNull(job);
|
|
|
|
var executor = scope.ServiceProvider.GetRequiredService<AgentRunExecutor>();
|
|
await executor.ProcessAsync(job!);
|
|
}
|
|
|
|
// The next claim finds nothing (the only job is done).
|
|
await using (var scope = factory.Services.CreateAsyncScope())
|
|
{
|
|
var queue = scope.ServiceProvider.GetRequiredService<JobQueue>();
|
|
Assert.Null(await queue.ClaimNextAsync("test-worker"));
|
|
}
|
|
|
|
// With no agent configured for the random seat, the run reaches a terminal Failed state
|
|
// (the full assemble→model→parse path is covered by AssemblerRunTests).
|
|
var fetched = await client.GetFromJsonAsync<RunResponse>($"/api/assembler/runs/{run.Id}");
|
|
Assert.Equal("Failed", fetched!.Status);
|
|
Assert.Contains("not found", fetched.Error ?? string.Empty, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
}
|