refactor(tags): remove tag entity and all references

Drops TagEntity, TagRepository, and tag wiring across data layer, worker,
and UI. Adds RemoveTags migration to clean up schema.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-05-19 08:07:24 +02:00
parent 8d34db3f9b
commit 623ebf147b
42 changed files with 333 additions and 1118 deletions

View File

@@ -52,7 +52,6 @@ public sealed class ExternalMcpServiceTests : IDisposable
private readonly ClaudeDoDbContext _ctx;
private readonly TaskRepository _tasks;
private readonly ListRepository _lists;
private readonly TagRepository _tags;
private readonly ExternalFakeHubContext _hub = new();
private readonly HubBroadcaster _broadcaster;
@@ -61,7 +60,6 @@ public sealed class ExternalMcpServiceTests : IDisposable
_ctx = _db.CreateContext();
_tasks = new TaskRepository(_ctx);
_lists = new ListRepository(_ctx);
_tags = new TagRepository(_ctx);
_broadcaster = new HubBroadcaster(_hub);
}
@@ -89,12 +87,8 @@ public sealed class ExternalMcpServiceTests : IDisposable
return task;
}
// QueueService is needed by ExternalMcpService's constructor. For tests that
// only exercise UpdateTask / DeleteTask / SetTaskTags / ListTags / ListTags,
// we never call its WakeQueue/RunNow/CancelTask paths, so a real QueueService
// built with the same approach used in QueueServiceTests is sufficient.
private ExternalMcpService BuildSut(QueueService queue) =>
new(_tasks, _lists, queue, _broadcaster, _tags,
new(_tasks, _lists, queue, _broadcaster,
TaskStateServiceBuilder.Build(_db.CreateFactory()).State);
private QueueService CreateQueue()
@@ -129,54 +123,6 @@ public sealed class ExternalMcpServiceTests : IDisposable
Assert.NotNull(await _tasks.GetByIdAsync(task.Id));
}
[Fact]
public async Task ListTags_ReturnsSeededAndCustomTags()
{
var listId = await SeedListAsync();
var task = await SeedTaskAsync(listId);
await _tasks.SetTagsAsync(task.Id, new[] { "agent", "custom-tag" });
var queue = CreateQueue();
var sut = BuildSut(queue);
var tags = await sut.ListTags(CancellationToken.None);
Assert.Contains(tags, t => t.Name == "agent");
Assert.Contains(tags, t => t.Name == "custom-tag");
}
[Fact]
public async Task AddTask_WithTags_AttachesTags()
{
var listId = await SeedListAsync();
var queue = CreateQueue();
var sut = BuildSut(queue);
var dto = await sut.AddTask(
listId, "scope-creep handoff", "desc", "claude-cli",
queueImmediately: false,
tags: new[] { "agent", "custom" },
CancellationToken.None);
var tags = await _tasks.GetTagsAsync(dto.Id);
Assert.Contains(tags, t => t.Name == "agent");
Assert.Contains(tags, t => t.Name == "custom");
}
[Fact]
public async Task AddTask_NullTags_BehavesAsBefore()
{
var listId = await SeedListAsync();
var queue = CreateQueue();
var sut = BuildSut(queue);
var dto = await sut.AddTask(
listId, "no tags", null, "claude-cli",
queueImmediately: false, tags: null, CancellationToken.None);
Assert.Empty(await _tasks.GetTagsAsync(dto.Id));
}
[Fact]
public async Task UpdateTask_PatchesNonNullFieldsOnly()
{
@@ -185,29 +131,13 @@ public sealed class ExternalMcpServiceTests : IDisposable
var queue = CreateQueue();
var sut = BuildSut(queue);
var dto = await sut.UpdateTask(task.Id, "new title", null, null, null, CancellationToken.None);
var dto = await sut.UpdateTask(task.Id, "new title", null, null, CancellationToken.None);
Assert.Equal("new title", dto.Title);
var loaded = await _tasks.GetByIdAsync(task.Id);
Assert.Equal("new title", loaded!.Title);
}
[Fact]
public async Task UpdateTask_TagsReplaceFullSet()
{
var listId = await SeedListAsync();
var task = await SeedTaskAsync(listId);
await _tasks.SetTagsAsync(task.Id, new[] { "agent" });
var queue = CreateQueue();
var sut = BuildSut(queue);
await sut.UpdateTask(task.Id, null, null, null, new[] { "manual" }, CancellationToken.None);
var tags = await _tasks.GetTagsAsync(task.Id);
Assert.Single(tags);
Assert.Equal("manual", tags[0].Name);
}
[Fact]
public async Task UpdateTask_OnRunning_Throws()
{
@@ -217,7 +147,7 @@ public sealed class ExternalMcpServiceTests : IDisposable
var sut = BuildSut(queue);
await Assert.ThrowsAsync<InvalidOperationException>(() =>
sut.UpdateTask(task.Id, "x", null, null, null, CancellationToken.None));
sut.UpdateTask(task.Id, "x", null, null, CancellationToken.None));
}
[Fact]
@@ -227,15 +157,14 @@ public sealed class ExternalMcpServiceTests : IDisposable
var sut = BuildSut(queue);
await Assert.ThrowsAsync<InvalidOperationException>(() =>
sut.UpdateTask("does-not-exist", "x", null, null, null, CancellationToken.None));
sut.UpdateTask("does-not-exist", "x", null, null, CancellationToken.None));
}
[Fact]
public async Task DeleteTask_RemovesTaskAndTagJoins()
public async Task DeleteTask_RemovesTask()
{
var listId = await SeedListAsync();
var task = await SeedTaskAsync(listId);
await _tasks.SetTagsAsync(task.Id, new[] { "agent" });
var queue = CreateQueue();
var sut = BuildSut(queue);
@@ -265,34 +194,4 @@ public sealed class ExternalMcpServiceTests : IDisposable
await Assert.ThrowsAsync<InvalidOperationException>(() =>
sut.DeleteTask("does-not-exist", CancellationToken.None));
}
[Fact]
public async Task SetTaskTags_ReplacesTagSetAndBroadcasts()
{
var listId = await SeedListAsync();
var task = await SeedTaskAsync(listId);
await _tasks.SetTagsAsync(task.Id, new[] { "agent" });
var queue = CreateQueue();
var sut = BuildSut(queue);
var dto = await sut.SetTaskTags(task.Id, new[] { "manual" }, CancellationToken.None);
var tags = await _tasks.GetTagsAsync(task.Id);
Assert.Single(tags);
Assert.Equal("manual", tags[0].Name);
Assert.Contains(_hub.RecordingClients.Proxy.Calls,
c => c.Method == "TaskUpdated" && (string)c.Args[0]! == task.Id);
}
[Fact]
public async Task SetTaskTags_OnRunning_Throws()
{
var listId = await SeedListAsync();
var task = await SeedTaskAsync(listId, status: TaskStatus.Running);
var queue = CreateQueue();
var sut = BuildSut(queue);
await Assert.ThrowsAsync<InvalidOperationException>(() =>
sut.SetTaskTags(task.Id, new[] { "manual" }, CancellationToken.None));
}
}