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

@@ -10,13 +10,11 @@ public sealed class ListRepositoryTests : IDisposable
private readonly DbFixture _db = new();
private readonly ClaudeDoDbContext _ctx;
private readonly ListRepository _lists;
private readonly TagRepository _tags;
public ListRepositoryTests()
{
_ctx = _db.CreateContext();
_lists = new ListRepository(_ctx);
_tags = new TagRepository(_ctx);
}
public void Dispose()
@@ -95,20 +93,4 @@ public sealed class ListRepositoryTests : IDisposable
Assert.True(all.Count >= 2);
}
[Fact]
public async Task TagJunction_AddAndRemove()
{
var listId = Guid.NewGuid().ToString();
await _lists.AddAsync(new ListEntity { Id = listId, Name = "Tagged", CreatedAt = DateTime.UtcNow });
var tagId = await _tags.GetOrCreateAsync("agent");
await _lists.AddTagAsync(listId, tagId);
var tags = await _lists.GetTagsAsync(listId);
Assert.Single(tags);
Assert.Equal("agent", tags[0].Name);
await _lists.RemoveTagAsync(listId, tagId);
tags = await _lists.GetTagsAsync(listId);
Assert.Empty(tags);
}
}

View File

@@ -68,7 +68,7 @@ public sealed class TaskRepositoryOrphanGuardTests : IDisposable
await _tasks.AddAsync(parent);
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
() => _tasks.CreateChildAsync(parent.Id, "child", null, null, null));
() => _tasks.CreateChildAsync(parent.Id, "child", null, null));
Assert.Contains("not in a planning phase", ex.Message);
}
@@ -78,7 +78,7 @@ public sealed class TaskRepositoryOrphanGuardTests : IDisposable
var listId = await CreateListAsync();
var parent = await SeedPlanningParentAsync(listId);
var child = await _tasks.CreateChildAsync(parent.Id, "child", null, null, null);
var child = await _tasks.CreateChildAsync(parent.Id, "child", null, null);
Assert.Equal(parent.Id, child.ParentTaskId);
Assert.Equal(TaskStatus.Idle, child.Status);
}
@@ -101,8 +101,8 @@ public sealed class TaskRepositoryOrphanGuardTests : IDisposable
{
var listId = await CreateListAsync();
var parent = await SeedPlanningParentAsync(listId);
await _tasks.CreateChildAsync(parent.Id, "a", null, null, null);
await _tasks.CreateChildAsync(parent.Id, "b", null, null, null);
await _tasks.CreateChildAsync(parent.Id, "a", null, null);
await _tasks.CreateChildAsync(parent.Id, "b", null, null);
var outcome = await _tasks.DiscardPlanningAsync(parent.Id, dequeueQueuedChildren: false);
@@ -117,7 +117,7 @@ public sealed class TaskRepositoryOrphanGuardTests : IDisposable
{
var listId = await CreateListAsync();
var parent = await SeedPlanningParentAsync(listId);
var child = await _tasks.CreateChildAsync(parent.Id, "c", null, null, null);
var child = await _tasks.CreateChildAsync(parent.Id, "c", null, null);
await SetChildStatusAsync(child.Id, TaskStatus.Queued);
var outcome = await _tasks.DiscardPlanningAsync(parent.Id, dequeueQueuedChildren: false);
@@ -134,7 +134,7 @@ public sealed class TaskRepositoryOrphanGuardTests : IDisposable
{
var listId = await CreateListAsync();
var parent = await SeedPlanningParentAsync(listId);
var child = await _tasks.CreateChildAsync(parent.Id, "c", null, null, null);
var child = await _tasks.CreateChildAsync(parent.Id, "c", null, null);
await SetChildStatusAsync(child.Id, TaskStatus.Queued);
var outcome = await _tasks.DiscardPlanningAsync(parent.Id, dequeueQueuedChildren: true);
@@ -149,7 +149,7 @@ public sealed class TaskRepositoryOrphanGuardTests : IDisposable
{
var listId = await CreateListAsync();
var parent = await SeedPlanningParentAsync(listId);
var child = await _tasks.CreateChildAsync(parent.Id, "c", null, null, null);
var child = await _tasks.CreateChildAsync(parent.Id, "c", null, null);
await SetChildStatusAsync(child.Id, TaskStatus.Running);
var outcome = await _tasks.DiscardPlanningAsync(parent.Id, dequeueQueuedChildren: true);
@@ -164,8 +164,8 @@ public sealed class TaskRepositoryOrphanGuardTests : IDisposable
{
var listId = await CreateListAsync();
var parent = await SeedPlanningParentAsync(listId);
var done = await _tasks.CreateChildAsync(parent.Id, "done", null, null, null);
var failed = await _tasks.CreateChildAsync(parent.Id, "failed", null, null, null);
var done = await _tasks.CreateChildAsync(parent.Id, "done", null, null);
var failed = await _tasks.CreateChildAsync(parent.Id, "failed", null, null);
await SetChildStatusAsync(done.Id, TaskStatus.Done);
await SetChildStatusAsync(failed.Id, TaskStatus.Failed);
@@ -220,7 +220,7 @@ public sealed class TaskRepositoryOrphanGuardTests : IDisposable
{
var listId = await CreateListAsync();
var parent = await SeedPlanningParentAsync(listId);
var child = await _tasks.CreateChildAsync(parent.Id, "c", null, null, null);
var child = await _tasks.CreateChildAsync(parent.Id, "c", null, null);
var dequeued = await _tasks.DequeueOrphanedChildrenAsync();
Assert.Equal(0, dequeued);

View File

@@ -12,14 +12,12 @@ public sealed class TaskRepositoryPlanningTests : IDisposable
private readonly ClaudeDoDbContext _ctx;
private readonly TaskRepository _tasks;
private readonly ListRepository _lists;
private readonly TagRepository _tags;
public TaskRepositoryPlanningTests()
{
_ctx = _db.CreateContext();
_tasks = new TaskRepository(_ctx);
_lists = new ListRepository(_ctx);
_tags = new TagRepository(_ctx);
}
public void Dispose()
@@ -97,7 +95,6 @@ public sealed class TaskRepositoryPlanningTests : IDisposable
parent.Id,
title: "child title",
description: "child desc",
tagNames: new[] { "agent" },
commitType: "feat");
Assert.Equal(TaskStatus.Idle, child.Status);
@@ -110,9 +107,6 @@ public sealed class TaskRepositoryPlanningTests : IDisposable
var loaded = await _tasks.GetByIdAsync(child.Id);
Assert.NotNull(loaded);
Assert.Equal(TaskStatus.Idle, loaded!.Status);
var tags = await _tasks.GetTagsAsync(child.Id);
Assert.Contains(tags, t => t.Name == "agent");
}
[Fact]
@@ -122,7 +116,7 @@ public sealed class TaskRepositoryPlanningTests : IDisposable
_ = listId;
await Assert.ThrowsAsync<InvalidOperationException>(() =>
_tasks.CreateChildAsync("nonexistent-parent-id", "t", null, null, null));
_tasks.CreateChildAsync("nonexistent-parent-id", "t", null, null));
}
[Fact]
@@ -202,8 +196,8 @@ public sealed class TaskRepositoryPlanningTests : IDisposable
await _tasks.AddAsync(parent);
await _tasks.SetPlanningStartedAsync(parent.Id, "tok");
await _tasks.UpdatePlanningSessionIdAsync(parent.Id, "claude-42");
var c1 = await _tasks.CreateChildAsync(parent.Id, "c1", null, null, null);
var c2 = await _tasks.CreateChildAsync(parent.Id, "c2", null, null, null);
var c1 = await _tasks.CreateChildAsync(parent.Id, "c1", null, null);
var c2 = await _tasks.CreateChildAsync(parent.Id, "c2", null, null);
var outcome = await _tasks.DiscardPlanningAsync(parent.Id, dequeueQueuedChildren: false);
@@ -237,7 +231,7 @@ public sealed class TaskRepositoryPlanningTests : IDisposable
var listId = await CreateListAsync();
var parent = MakeTask(listId, phase: PlanningPhase.Active);
await _tasks.AddAsync(parent);
await _tasks.CreateChildAsync(parent.Id, "c", null, null, null);
await _tasks.CreateChildAsync(parent.Id, "c", null, null);
await Assert.ThrowsAsync<Microsoft.Data.Sqlite.SqliteException>(async () =>
{

View File

@@ -12,14 +12,12 @@ public sealed class TaskRepositoryTests : IDisposable
private readonly ClaudeDoDbContext _ctx;
private readonly TaskRepository _tasks;
private readonly ListRepository _lists;
private readonly TagRepository _tags;
public TaskRepositoryTests()
{
_ctx = _db.CreateContext();
_tasks = new TaskRepository(_ctx);
_lists = new ListRepository(_ctx);
_tags = new TagRepository(_ctx);
}
public void Dispose()
@@ -239,83 +237,4 @@ public sealed class TaskRepositoryTests : IDisposable
Assert.Equal(0, reloadB!.SortOrder);
}
[Fact]
public async Task GetEffectiveTagsAsync_Returns_Union_Of_ListTags_And_TaskTags()
{
var listId = await CreateListAsync();
var agentTagId = await _tags.GetOrCreateAsync("agent");
var manualTagId = await _tags.GetOrCreateAsync("manual");
var codeTagId = await _tags.GetOrCreateAsync("code");
await _lists.AddTagAsync(listId, agentTagId);
var task = MakeTask(listId);
await _tasks.AddAsync(task);
await _tasks.AddTagAsync(task.Id, manualTagId);
await _tasks.AddTagAsync(task.Id, codeTagId);
var effective = await _tasks.GetEffectiveTagsAsync(task.Id);
var names = effective.Select(t => t.Name).OrderBy(n => n).ToList();
Assert.Equal(3, names.Count);
Assert.Contains("agent", names);
Assert.Contains("code", names);
Assert.Contains("manual", names);
}
[Fact]
public async Task SetTagsAsync_AttachesNewTagsAndCreatesMissingRows()
{
var listId = await CreateListAsync("L");
var task = MakeTask(listId);
await _tasks.AddAsync(task);
await _tasks.SetTagsAsync(task.Id, new[] { "agent", "novel-tag" });
var tags = await _tasks.GetTagsAsync(task.Id);
Assert.Contains(tags, t => t.Name == "agent");
Assert.Contains(tags, t => t.Name == "novel-tag");
Assert.Equal(2, tags.Count);
}
[Fact]
public async Task SetTagsAsync_ReplacesExistingTagSet()
{
var listId = await CreateListAsync("L");
var task = MakeTask(listId);
await _tasks.AddAsync(task);
await _tasks.SetTagsAsync(task.Id, new[] { "agent" });
await _tasks.SetTagsAsync(task.Id, new[] { "manual" });
var tags = await _tasks.GetTagsAsync(task.Id);
Assert.Single(tags);
Assert.Equal("manual", tags[0].Name);
}
[Fact]
public async Task SetTagsAsync_DeduplicatesCaseInsensitively()
{
var listId = await CreateListAsync("L");
var task = MakeTask(listId);
await _tasks.AddAsync(task);
await _tasks.SetTagsAsync(task.Id, new[] { "agent", "AGENT", "Agent" });
var tags = await _tasks.GetTagsAsync(task.Id);
Assert.Single(tags);
}
[Fact]
public async Task SetTagsAsync_EmptyListClearsAllTags()
{
var listId = await CreateListAsync("L");
var task = MakeTask(listId);
await _tasks.AddAsync(task);
await _tasks.SetTagsAsync(task.Id, new[] { "agent" });
await _tasks.SetTagsAsync(task.Id, Array.Empty<string>());
Assert.Empty(await _tasks.GetTagsAsync(task.Id));
}
}