Stufe 4c (#764): Bezeichner auf Englisch - Typen, Methoden, Felder, Parameter, Locals, Testnamen
Mit Roslyn umbenannt (Verweise folgen, Zeichenketten bleiben unberuehrt). Nicht umbenannt, weil gespeichert oder ueber die API gebunden (Stufe 4e): Properties, Record-Parameter, Enum-Member sowie die Parameter der Endpunkt-Handler (Routen-/Query-Bindung ueber den Namen). Die JSON-Felder anonymer Objekte bleiben ausdruecklich benannt (JsonFilter: pfad). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
837a634a22
commit
77062facc9
@@ -0,0 +1,173 @@
|
||||
using System.Text.Json;
|
||||
using ClawdDotNet.Core.State;
|
||||
using ClawdDotNet.Core.Storage;
|
||||
using ClawdDotNet.Core.Tasks;
|
||||
using ClawdDotNet.Core.Tools;
|
||||
using ClawdDotNet.Tools.Taskboard;
|
||||
using ClawdDotNet.Tools.Tests.FileRW;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Shouldly;
|
||||
|
||||
namespace ClawdDotNet.Tools.Tests.Taskboard;
|
||||
|
||||
/// <summary>
|
||||
/// task_find and the duplicate brake (#674).
|
||||
///
|
||||
/// Occasion from test operation: the operations check runs every three hours and created
|
||||
/// again at every run what was already open from the previous one — over thirty tasks
|
||||
/// for @human, among them the same price finding six times.
|
||||
/// </summary>
|
||||
public sealed class DuplicateTests : IDisposable
|
||||
{
|
||||
private readonly string _verzeichnis;
|
||||
private readonly SqliteStorage _storage;
|
||||
private readonly SqliteTaskRepository _repo;
|
||||
private readonly TaskboardService _board;
|
||||
private readonly TaskboardTool _tool = new();
|
||||
|
||||
public DuplicateTests()
|
||||
{
|
||||
_verzeichnis = Path.Combine(Path.GetTempPath(), "clawd-tests", Guid.NewGuid().ToString("N"));
|
||||
_storage = new SqliteStorage(Path.Combine(_verzeichnis, "state.db"));
|
||||
_repo = new SqliteTaskRepository(_storage);
|
||||
_board = new TaskboardService(_repo, Path.Combine(_verzeichnis, "SharedWorkspace", "tasks"));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ClawdDotNet.Core.Storage.SqliteStorage.ReleasePooledConnections(_verzeichnis);
|
||||
try { Directory.Delete(_verzeichnis, recursive: true); }
|
||||
catch { /* Cleaning up is secondary */ }
|
||||
}
|
||||
|
||||
private AgentToolContext Context() => new(
|
||||
"hausmeister", "inst",
|
||||
new Dictionary<string, object?>(),
|
||||
new NullStateStore(),
|
||||
NullLogger.Instance,
|
||||
CancellationToken.None,
|
||||
SharedWorkspacePath: Path.Combine(_verzeichnis, "SharedWorkspace"),
|
||||
Tasks: _repo);
|
||||
|
||||
private Task<ToolResult> Run(string json)
|
||||
=> _tool.ExecuteAsync(JsonDocument.Parse(json).RootElement, Context(), CancellationToken.None);
|
||||
|
||||
private Task<TaskItem> Create(string title, TaskItemStatus status = TaskItemStatus.Todo)
|
||||
=> _board.CreateAsync(new TaskItem { Title = title, Status = status, Assignee = "@human", Body = "Beobachtet." }, default);
|
||||
|
||||
// ─── task_find ───
|
||||
|
||||
[Fact]
|
||||
public async Task Without_matches_the_answer_says_creating_is_allowed()
|
||||
{
|
||||
await Create("Betriebsluecke: Wetter Warnungen veraltet");
|
||||
|
||||
var result = await Run("""{"action":"task_find","title":"Betriebsluecke: Eur Kurse nicht aktuell"}""");
|
||||
|
||||
result.Success.ShouldBeTrue(result.ErrorMessage);
|
||||
result.Content.ShouldContain("No open task resembles");
|
||||
result.Content.ShouldContain("task_create");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task The_same_finding_is_found_with_id_age_and_classification()
|
||||
{
|
||||
var present = await Create("Betriebsluecke: Eur Kurse nicht aktuell");
|
||||
|
||||
var result = await Run("""{"action":"task_find","title":"Betriebsluecke: Kurse EUR nicht aktuell"}""");
|
||||
|
||||
result.Success.ShouldBeTrue(result.ErrorMessage);
|
||||
result.Content.ShouldContain(present.Id);
|
||||
result.Content.ShouldContain("same issue");
|
||||
result.Content.ShouldContain("open since");
|
||||
result.Content.ShouldContain("task_comment", Case.Sensitive, "der Agent soll ergaenzen, nicht neu anlegen");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Without_title_the_tool_asks()
|
||||
{
|
||||
var result = await Run("""{"action":"task_find"}""");
|
||||
|
||||
result.Success.ShouldBeFalse();
|
||||
result.ErrorMessage.ShouldNotBeNull().ShouldContain("'title' is required");
|
||||
}
|
||||
|
||||
// ─── the brake when creating ───
|
||||
|
||||
[Fact]
|
||||
public async Task A_second_identical_task_is_not_created()
|
||||
{
|
||||
var first = await Create("Betriebsluecke: Eur Kurse nicht aktuell");
|
||||
|
||||
var result = await Run("""{"action":"task_create","title":"Betriebsluecke: Kurse EUR nicht aktuell","assignee":"@human"}""");
|
||||
|
||||
result.Success.ShouldBeFalse();
|
||||
result.ErrorMessage.ShouldNotBeNull().ShouldContain(first.Id);
|
||||
result.ErrorMessage.ShouldContain("recorded");
|
||||
|
||||
(await _board.ListAsync(new TaskQuery { Limit = 50 }, default)).Count
|
||||
.ShouldBe(1, "es bleibt bei der einen Aufgabe");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task The_recurrence_is_then_on_the_existing_task()
|
||||
{
|
||||
var first = await Create("Betriebsluecke: Nachrichten fehlen");
|
||||
|
||||
await Run("""{"action":"task_create","title":"Betriebsluecke: Nachrichten fehlen","assignee":"@human"}""");
|
||||
|
||||
var datei = await File.ReadAllTextAsync(Path.Combine(_board.TasksDirectory, (await _board.GetAsync(first.Id, default))!.FileName));
|
||||
datei.ShouldContain("Observed again");
|
||||
datei.ShouldContain("hausmeister", Case.Insensitive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_different_matter_comes_through_normally()
|
||||
{
|
||||
await Create("Betriebsluecke: Eur Kurse nicht aktuell");
|
||||
|
||||
var result = await Run("""{"action":"task_create","title":"Betriebsluecke: Wetter Warnungen stand.md veraltet","assignee":"@human"}""");
|
||||
|
||||
result.Success.ShouldBeTrue(result.ErrorMessage);
|
||||
(await _board.ListAsync(new TaskQuery { Limit = 50 }, default)).Count.ShouldBe(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task With_force_the_agent_may_create_anyway()
|
||||
{
|
||||
await Create("Betriebsluecke: Eur Kurse nicht aktuell");
|
||||
|
||||
var result = await Run("""{"action":"task_create","title":"Betriebsluecke: Eur Kurse nicht aktuell","assignee":"@human","force":true}""");
|
||||
|
||||
result.Success.ShouldBeTrue(result.ErrorMessage);
|
||||
(await _board.ListAsync(new TaskQuery { Limit = 50 }, default)).Count.ShouldBe(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task The_duplicate_is_found_even_behind_500_other_tasks()
|
||||
{
|
||||
// #716: The search read ListAsync with a limit of 500, sorted by priority. An old
|
||||
// task with low priority thus fell out of sight as soon as the board grew.
|
||||
var old = await _board.CreateAsync(new TaskItem
|
||||
{
|
||||
Title = "Betriebsluecke: Eur Kurse nicht aktuell", Assignee = "@human", Priority = 1
|
||||
}, default);
|
||||
for (var i = 0; i < 510; i++)
|
||||
await _repo.UpsertAsync(new TaskItem { Id = $"t-fill-{i}", Title = $"Anderes Thema {i}", Priority = 5 }, default);
|
||||
|
||||
var result = await Run("""{"action":"task_create","title":"Betriebsluecke: Kurse EUR nicht aktuell","assignee":"@human"}""");
|
||||
|
||||
result.Success.ShouldBeFalse("die alte Aufgabe meint dasselbe");
|
||||
result.ErrorMessage.ShouldNotBeNull().ShouldContain(old.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_done_task_does_not_block_the_next_finding()
|
||||
{
|
||||
await Create("Betriebsluecke: Eur Kurse nicht aktuell", TaskItemStatus.Done);
|
||||
|
||||
var result = await Run("""{"action":"task_create","title":"Betriebsluecke: Eur Kurse nicht aktuell","assignee":"@human"}""");
|
||||
|
||||
result.Success.ShouldBeTrue(result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user