using ClaudeDo.Data; using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; using Microsoft.EntityFrameworkCore; using TaskStatus = ClaudeDo.Data.Models.TaskStatus; namespace ClaudeDo.Data.Tests; public sealed class TaskAttachmentRepositoryTests : IDisposable { private readonly string _dbPath; private readonly ClaudeDoDbContext _ctx; private readonly TaskAttachmentRepository _repo; private const string ListId = "l1"; private const string TaskId = "t1"; public TaskAttachmentRepositoryTests() { _dbPath = Path.Combine(Path.GetTempPath(), $"claudedo_att_{Guid.NewGuid():N}.db"); var options = new DbContextOptionsBuilder() .UseSqlite($"Data Source={_dbPath}") .Options; _ctx = new ClaudeDoDbContext(options); _ctx.Database.EnsureCreated(); _repo = new TaskAttachmentRepository(_ctx); _ctx.Lists.Add(new ListEntity { Id = ListId, Name = "Test", CreatedAt = DateTime.UtcNow }); _ctx.Tasks.Add(new TaskEntity { Id = TaskId, ListId = ListId, Title = "T", Status = TaskStatus.Idle, CreatedAt = DateTime.UtcNow }); _ctx.SaveChanges(); } public void Dispose() { _ctx.Dispose(); foreach (var suffix in new[] { "", "-wal", "-shm" }) try { File.Delete(_dbPath + suffix); } catch { } } private TaskAttachmentEntity MakeAttachment(string fileName) => new() { Id = Guid.NewGuid().ToString(), TaskId = TaskId, FileName = fileName, ByteSize = 100, CreatedAt = DateTime.UtcNow, }; [Fact] public async Task Add_ListByTaskId_Delete_roundtrip() { var a1 = MakeAttachment("file1.txt"); var a2 = MakeAttachment("file2.txt"); await _repo.AddAsync(a1); await _repo.AddAsync(a2); var list = await _repo.ListByTaskIdAsync(TaskId); Assert.Equal(2, list.Count); Assert.Contains(list, a => a.FileName == "file1.txt"); Assert.Contains(list, a => a.FileName == "file2.txt"); await _repo.DeleteAsync(TaskId, "file1.txt"); var afterDelete = await _repo.ListByTaskIdAsync(TaskId); Assert.Single(afterDelete); Assert.Equal("file2.txt", afterDelete[0].FileName); } [Fact] public async Task GetAsync_returns_correct_entity() { var a = MakeAttachment("target.txt"); await _repo.AddAsync(a); var found = await _repo.GetAsync(TaskId, "target.txt"); Assert.NotNull(found); Assert.Equal(a.Id, found!.Id); } [Fact] public async Task GetAsync_returns_null_when_missing() { var result = await _repo.GetAsync(TaskId, "nope.txt"); Assert.Null(result); } [Fact] public async Task DeleteAllForTask_clears_all_rows_for_task() { await _repo.AddAsync(MakeAttachment("a.txt")); await _repo.AddAsync(MakeAttachment("b.txt")); await _repo.AddAsync(MakeAttachment("c.txt")); await _repo.DeleteAllForTaskAsync(TaskId); var list = await _repo.ListByTaskIdAsync(TaskId); Assert.Empty(list); } [Fact] public async Task ListByTaskId_ordered_by_created_at() { var base_ = DateTime.UtcNow; var a1 = new TaskAttachmentEntity { Id = Guid.NewGuid().ToString(), TaskId = TaskId, FileName = "first.txt", ByteSize = 1, CreatedAt = base_ }; var a2 = new TaskAttachmentEntity { Id = Guid.NewGuid().ToString(), TaskId = TaskId, FileName = "second.txt", ByteSize = 1, CreatedAt = base_.AddSeconds(1) }; await _repo.AddAsync(a2); await _repo.AddAsync(a1); var list = await _repo.ListByTaskIdAsync(TaskId); Assert.Equal("first.txt", list[0].FileName); Assert.Equal("second.txt", list[1].FileName); } }