Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ All notable changes to this project are recorded here. The format follows

### Added

- Integration coverage for the hourly stuck-work report, including expired task claims and stale findings.
- Docker image published to `ghcr.io/ahmad-codex/workstream-mcp` on version tags; the quick-start compose file can use it.
- Dev container / Codespaces setup with .NET 10 and Postgres 16, and a `Makefile`.
- CODEOWNERS, question issue form, Discussions templates, path labeler, first-interaction greeting, stale policy and release-drafter.
Expand Down
3 changes: 3 additions & 0 deletions src/Workstream.Data/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Workstream.IntegrationTests")]
2 changes: 1 addition & 1 deletion src/Workstream.Data/StuckWorkJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
}
}

private async Task RunOnceAsync(CancellationToken ct)
internal async Task RunOnceAsync(CancellationToken ct)
{
await using var conn = await _factory.OpenAsync(ct).ConfigureAwait(false);
// Inline a single query that bundles the four categories from §11.3.
Expand Down
53 changes: 53 additions & 0 deletions tests/Workstream.IntegrationTests/StuckWorkJobTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Threading.Tasks;
using Dapper;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Workstream.Data.StuckWork;
using Xunit;

namespace Workstream.IntegrationTests;

public sealed class StuckWorkJobTests : IClassFixture<PostgresFixture>
{
private readonly PostgresFixture _pg;

public StuckWorkJobTests(PostgresFixture pg) => _pg = pg;

[Fact]
public async Task RunOnceAsync_WritesCountsForExpiredTaskClaimAndStaleFinding()
{
await using var conn = await _pg.ConnectionFactory.OpenAsync().ConfigureAwait(false);
var projectId = Guid.NewGuid();
var planId = Guid.NewGuid();
var taskId = Guid.NewGuid();
var actorId = Guid.NewGuid();

await conn.ExecuteAsync("""
INSERT INTO projects (id, slug, display_name) VALUES (@projectId, @slug, 'Stuck work');
INSERT INTO plans (id, project_id, plan_type_id, name, status) VALUES (@planId, @projectId, 'audit', 'Stuck work', 'active');
INSERT INTO users (id, github_username, mcp_url_token, actor_type)
VALUES (@actorId, @username, @token, 'subagent');
INSERT INTO tasks (id, plan_id, external_key, title, status, claim_actor_id, claim_role, claim_token, claimed_at, claimed_until)
VALUES (@taskId, @planId, 'STUCK-1', 'Expired task', 'in_progress', @actorId, 'developer', gen_random_uuid(), now() - interval '2 hours', now() - interval '1 hour');
INSERT INTO findings (task_id, external_key, status, created_at)
VALUES (@taskId, 'F-STUCK-1', 'pending_verification', now() - interval '25 hours');
""", new
{
projectId,
planId,
taskId,
actorId,
slug = $"stuck-{projectId:N}",
username = $"stuck-{actorId:N}",
token = $"token-{actorId:N}",
}).ConfigureAwait(false);

var job = new StuckWorkJob(_pg.ConnectionFactory, NullLogger<StuckWorkJob>.Instance);
await job.RunOnceAsync(default).ConfigureAwait(false);

var summary = await conn.QuerySingleAsync<string>("SELECT summary::text FROM stuck_work_reports ORDER BY id DESC LIMIT 1").ConfigureAwait(false);
summary.Should().Contain("\"stuck_tasks\": 1");
summary.Should().Contain("\"stuck_findings\": 1");
}
}
Loading