Files
kandev/apps/backend/internal/task/repository/sqlite/attachment_test.go
T
2026-08-04 22:11:13 +01:00

148 lines
6.3 KiB
Go

package sqlite
import (
"context"
"testing"
"time"
"github.com/kandev/kandev/internal/task/models"
)
func TestClaimMessageAttachments_IsIdempotentForSameTaskSession(t *testing.T) {
repo := newRepoForSessionTests(t)
ctx := context.Background()
seedWorkspace(t, repo, "workspace-attachments")
now := time.Now().UTC()
attachment := &models.TaskMessageAttachment{
ID: "attachment-idempotent", OwnerID: "owner-1", WorkspaceID: "workspace-attachments",
Name: "bundle.zip", MimeType: "application/zip", Kind: "resource", DeliveryMode: "path",
SizeBytes: 16, StorageKey: "attachment-idempotent", State: models.AttachmentStateStaged,
ExpiresAt: now.Add(time.Hour), CreatedAt: now,
}
if err := repo.CreateMessageAttachment(ctx, attachment); err != nil {
t.Fatal(err)
}
if err := repo.ClaimMessageAttachments(ctx, []string{attachment.ID}, "owner-1", "workspace-attachments", "task-1", "session-1"); err != nil {
t.Fatalf("initial claim: %v", err)
}
if err := repo.ClaimMessageAttachments(ctx, []string{attachment.ID}, "owner-1", "workspace-attachments", "task-1", "session-1"); err != nil {
t.Fatalf("idempotent claim: %v", err)
}
got, err := repo.GetMessageAttachment(ctx, attachment.ID)
if err != nil {
t.Fatal(err)
}
if got.State != models.AttachmentStateClaimed || got.TaskID != "task-1" || got.SessionID != "session-1" {
t.Fatalf("claimed attachment = %+v", got)
}
}
func TestClaimMessageAttachments_AllowsSeparateSubmissionsToReachTheLimit(t *testing.T) {
repo := newRepoForSessionTests(t)
ctx := context.Background()
seedWorkspace(t, repo, "workspace-attachments")
now := time.Now().UTC()
first := &models.TaskMessageAttachment{
ID: "attachment-first", OwnerID: "owner-1", WorkspaceID: "workspace-attachments",
Name: "first.bin", MimeType: "application/octet-stream", Kind: "resource", DeliveryMode: "path",
SizeBytes: models.MaxMessageAttachmentBytes * 3 / 5, StorageKey: "attachment-first", State: models.AttachmentStateStaged,
ExpiresAt: now.Add(time.Hour), CreatedAt: now,
}
second := *first
second.ID = "attachment-second"
second.Name = "second.bin"
second.StorageKey = second.ID
for _, attachment := range []*models.TaskMessageAttachment{first, &second} {
if err := repo.CreateMessageAttachment(ctx, attachment); err != nil {
t.Fatal(err)
}
}
if err := repo.ClaimMessageAttachments(ctx, []string{first.ID}, "owner-1", "workspace-attachments", "task-1", "session-1"); err != nil {
t.Fatalf("first claim: %v", err)
}
if err := repo.ClaimMessageAttachments(ctx, []string{second.ID}, "owner-1", "workspace-attachments", "task-1", "session-1"); err != nil {
t.Fatalf("second claim: %v", err)
}
}
func TestClaimMessageAttachments_RejectsExpiredStagedDescriptor(t *testing.T) {
repo := newRepoForSessionTests(t)
ctx := context.Background()
seedWorkspace(t, repo, "workspace-attachments")
now := time.Now().UTC()
attachment := &models.TaskMessageAttachment{
ID: "attachment-expired", OwnerID: "owner-1", WorkspaceID: "workspace-attachments",
Name: "expired.bin", MimeType: "application/octet-stream", Kind: "resource", DeliveryMode: "path",
SizeBytes: 1, StorageKey: "attachment-expired", State: models.AttachmentStateStaged,
ExpiresAt: now.Add(-time.Minute), CreatedAt: now.Add(-time.Hour),
}
if err := repo.CreateMessageAttachment(ctx, attachment); err != nil {
t.Fatal(err)
}
if err := repo.ClaimMessageAttachments(ctx, []string{attachment.ID}, "owner-1", "workspace-attachments", "task-1", "session-1"); err == nil {
t.Fatal("expected expired attachment claim to fail")
}
got, err := repo.GetMessageAttachment(ctx, attachment.ID)
if err != nil {
t.Fatal(err)
}
if got.State != models.AttachmentStateStaged {
t.Fatalf("expired attachment state = %q, want staged", got.State)
}
}
func TestDeleteClaimedMessageAttachments_RemovesOnlyMatchingClaims(t *testing.T) {
repo := newRepoForSessionTests(t)
ctx := context.Background()
seedWorkspace(t, repo, "workspace-attachments")
now := time.Now().UTC()
attachments := []*models.TaskMessageAttachment{
{ID: "release-one", OwnerID: "owner-1", WorkspaceID: "workspace-attachments", TaskID: "task-1", SessionID: "session-1", Name: "one", MimeType: "text/plain", Kind: "resource", DeliveryMode: "path", SizeBytes: 1, StorageKey: "release-one", State: models.AttachmentStateClaimed, ExpiresAt: now.Add(time.Hour), CreatedAt: now},
{ID: "release-other-task", OwnerID: "owner-1", WorkspaceID: "workspace-attachments", TaskID: "task-2", SessionID: "session-1", Name: "two", MimeType: "text/plain", Kind: "resource", DeliveryMode: "path", SizeBytes: 1, StorageKey: "release-other-task", State: models.AttachmentStateClaimed, ExpiresAt: now.Add(time.Hour), CreatedAt: now},
}
for _, attachment := range attachments {
if err := repo.CreateMessageAttachment(ctx, attachment); err != nil {
t.Fatal(err)
}
}
released, err := repo.DeleteClaimedMessageAttachments(ctx, []string{"release-one", "release-other-task"}, "owner-1", "task-1", "session-1")
if err != nil {
t.Fatal(err)
}
if len(released) != 1 || released[0].ID != "release-one" {
t.Fatalf("released = %+v", released)
}
if _, err := repo.GetMessageAttachment(ctx, "release-one"); err == nil {
t.Fatal("released attachment still exists")
}
if _, err := repo.GetMessageAttachment(ctx, "release-other-task"); err != nil {
t.Fatalf("unmatched claim was removed: %v", err)
}
}
func TestDeleteMessageAttachmentsByTask_RemovesRegistryRows(t *testing.T) {
repo := newRepoForSessionTests(t)
ctx := context.Background()
seedWorkspace(t, repo, "workspace-attachments")
now := time.Now().UTC()
attachment := &models.TaskMessageAttachment{
ID: "task-cleanup-attachment", OwnerID: "owner-1", WorkspaceID: "workspace-attachments", TaskID: "task-cleanup",
Name: "cleanup.bin", MimeType: "application/octet-stream", Kind: "resource", DeliveryMode: "path", SizeBytes: 1,
StorageKey: "task-cleanup-attachment", State: models.AttachmentStateClaimed, ExpiresAt: now.Add(time.Hour), CreatedAt: now,
}
if err := repo.CreateMessageAttachment(ctx, attachment); err != nil {
t.Fatal(err)
}
removed, err := repo.DeleteMessageAttachmentsByTask(ctx, "task-cleanup")
if err != nil {
t.Fatal(err)
}
if len(removed) != 1 || removed[0].ID != attachment.ID {
t.Fatalf("removed = %+v", removed)
}
if _, err := repo.GetMessageAttachment(ctx, attachment.ID); err == nil {
t.Fatal("task attachment still exists")
}
}