You've already forked agentic-coding-workflow
test(jobs/runners): cover partial-storage-success, list-error isolation, DB-delete-error branches
This commit is contained in:
@@ -14,31 +14,45 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type fakeAttachmentRepo struct {
|
type fakeAttachmentRepo struct {
|
||||||
softDel []runners.AttachmentRow
|
softDel []runners.AttachmentRow
|
||||||
orphan []runners.AttachmentRow
|
orphan []runners.AttachmentRow
|
||||||
deletedIDs []uuid.UUID
|
softDelListErr error
|
||||||
|
orphanListErr error
|
||||||
|
deleteErr error
|
||||||
|
deletedIDs []uuid.UUID
|
||||||
|
softDelCalled bool
|
||||||
|
orphanCalled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeAttachmentRepo) ListAttachmentsForSoftDeletedConversations(_ context.Context, _ time.Duration) ([]runners.AttachmentRow, error) {
|
func (f *fakeAttachmentRepo) ListAttachmentsForSoftDeletedConversations(_ context.Context, _ time.Duration) ([]runners.AttachmentRow, error) {
|
||||||
return f.softDel, nil
|
f.softDelCalled = true
|
||||||
|
return f.softDel, f.softDelListErr
|
||||||
}
|
}
|
||||||
func (f *fakeAttachmentRepo) ListExpiredOrphans(_ context.Context, _ time.Duration) ([]runners.AttachmentRow, error) {
|
func (f *fakeAttachmentRepo) ListExpiredOrphans(_ context.Context, _ time.Duration) ([]runners.AttachmentRow, error) {
|
||||||
return f.orphan, nil
|
f.orphanCalled = true
|
||||||
|
return f.orphan, f.orphanListErr
|
||||||
}
|
}
|
||||||
func (f *fakeAttachmentRepo) DeleteAttachments(_ context.Context, ids []uuid.UUID) error {
|
func (f *fakeAttachmentRepo) DeleteAttachments(_ context.Context, ids []uuid.UUID) error {
|
||||||
|
if f.deleteErr != nil {
|
||||||
|
return f.deleteErr
|
||||||
|
}
|
||||||
f.deletedIDs = append(f.deletedIDs, ids...)
|
f.deletedIDs = append(f.deletedIDs, ids...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type fakeStorage struct {
|
type fakeStorage struct {
|
||||||
deleted []string
|
deleted []string
|
||||||
err error
|
err error
|
||||||
|
failPaths map[string]error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *fakeStorage) Delete(_ context.Context, path string) error {
|
func (s *fakeStorage) Delete(_ context.Context, path string) error {
|
||||||
if s.err != nil {
|
if s.err != nil {
|
||||||
return s.err
|
return s.err
|
||||||
}
|
}
|
||||||
|
if e, ok := s.failPaths[path]; ok {
|
||||||
|
return e
|
||||||
|
}
|
||||||
s.deleted = append(s.deleted, path)
|
s.deleted = append(s.deleted, path)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -71,3 +85,52 @@ func TestAttachmentCleanup_StorageErrorDoesNotDeleteFromDB(t *testing.T) {
|
|||||||
require.Empty(t, st.deleted, "storage error → no path recorded")
|
require.Empty(t, st.deleted, "storage error → no path recorded")
|
||||||
require.Empty(t, repo.deletedIDs, "storage error → no DB delete batch")
|
require.Empty(t, repo.deletedIDs, "storage error → no DB delete batch")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAttachmentCleanup_PartialStorageSuccessOnlyDeletesSuccessfulIDs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
succID := uuid.New()
|
||||||
|
failID := uuid.New()
|
||||||
|
repo := &fakeAttachmentRepo{
|
||||||
|
softDel: []runners.AttachmentRow{
|
||||||
|
{ID: succID, StoragePath: "ok"},
|
||||||
|
{ID: failID, StoragePath: "bad"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
st := &fakeStorage{failPaths: map[string]error{"bad": errors.New("permission denied")}}
|
||||||
|
r := runners.NewAttachmentCleanup(repo, st, runners.AttachmentCleanupConfig{Retention: 30 * 24 * time.Hour}, discardLogger())
|
||||||
|
r.Run(context.Background())
|
||||||
|
assert.ElementsMatch(t, []string{"ok"}, st.deleted, "only successful storage delete recorded")
|
||||||
|
assert.ElementsMatch(t, []uuid.UUID{succID}, repo.deletedIDs, "DB delete batch only contains successfully-deleted-from-storage IDs")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAttachmentCleanup_SoftDelListErrorDoesNotPreventOrphanPhase(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
orphanID := uuid.New()
|
||||||
|
repo := &fakeAttachmentRepo{
|
||||||
|
softDelListErr: errors.New("db blip"),
|
||||||
|
orphan: []runners.AttachmentRow{{ID: orphanID, StoragePath: "p"}},
|
||||||
|
}
|
||||||
|
st := &fakeStorage{}
|
||||||
|
r := runners.NewAttachmentCleanup(repo, st, runners.AttachmentCleanupConfig{Retention: 30 * 24 * time.Hour}, discardLogger())
|
||||||
|
r.Run(context.Background())
|
||||||
|
assert.True(t, repo.softDelCalled, "soft-del list was attempted")
|
||||||
|
assert.True(t, repo.orphanCalled, "orphan list still ran after soft-del list error")
|
||||||
|
assert.ElementsMatch(t, []string{"p"}, st.deleted, "orphan storage delete proceeded")
|
||||||
|
assert.ElementsMatch(t, []uuid.UUID{orphanID}, repo.deletedIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAttachmentCleanup_DBDeleteErrorIsLoggedNotPanicked(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
id := uuid.New()
|
||||||
|
repo := &fakeAttachmentRepo{
|
||||||
|
softDel: []runners.AttachmentRow{{ID: id, StoragePath: "p"}},
|
||||||
|
deleteErr: errors.New("constraint violation"),
|
||||||
|
}
|
||||||
|
st := &fakeStorage{}
|
||||||
|
r := runners.NewAttachmentCleanup(repo, st, runners.AttachmentCleanupConfig{Retention: 30 * 24 * time.Hour}, discardLogger())
|
||||||
|
require.NotPanics(t, func() {
|
||||||
|
r.Run(context.Background())
|
||||||
|
}, "DB delete error must not panic")
|
||||||
|
assert.ElementsMatch(t, []string{"p"}, st.deleted, "storage delete proceeded before DB error")
|
||||||
|
assert.Empty(t, repo.deletedIDs, "deleteErr means no IDs accumulated in fake")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user