package service import ( "context" "encoding/json" "errors" "net/http" "testing" "time" "github.com/stretchr/testify/require" ) type imageTaskMemoryStore struct { task *ImageTaskRecord ttl time.Duration saveErr error getErr error } func (s *imageTaskMemoryStore) Save(_ context.Context, task *ImageTaskRecord, ttl time.Duration) error { if s.saveErr != nil { return s.saveErr } copy := *task s.task = © s.ttl = ttl return nil } func (s *imageTaskMemoryStore) Get(_ context.Context, _ string) (*ImageTaskRecord, error) { if s.getErr != nil { return nil, s.getErr } if s.task == nil { return nil, ErrImageTaskNotFound } copy := *s.task return ©, nil } func TestImageTaskServiceLifecycleAndOwnership(t *testing.T) { store := &imageTaskMemoryStore{} svc := NewImageTaskServiceWithOptions(store, time.Hour, 10*time.Minute) owner := ImageTaskOwner{UserID: 7, APIKeyID: 9} created, err := svc.Create(context.Background(), owner) require.NoError(t, err) require.Equal(t, ImageTaskStatusProcessing, created.Status) require.Equal(t, created.ID, created.TaskID) require.Equal(t, "image.generation.task", created.Object) require.Equal(t, time.Hour, store.ttl) require.Equal(t, owner.UserID, store.task.UserID) require.Equal(t, owner.APIKeyID, store.task.APIKeyID) _, err = svc.Get(context.Background(), ImageTaskOwner{UserID: 7, APIKeyID: 10}, created.ID) require.ErrorIs(t, err, ErrImageTaskNotFound) result := json.RawMessage(`{"created":123,"data":[{"url":"https://example.test/image.png"}]}`) require.NoError(t, svc.Complete(context.Background(), created.ID, http.StatusOK, result)) completed, err := svc.Get(context.Background(), owner, created.ID) require.NoError(t, err) require.Equal(t, ImageTaskStatusCompleted, completed.Status) require.Equal(t, http.StatusOK, completed.HTTPStatus) require.Equal(t, "https://example.test/image.png", completed.ImageURL) require.JSONEq(t, string(result), string(completed.Result)) require.NotNil(t, completed.CompletedAt) } func TestImageTaskServiceInvalidResultBecomesFailed(t *testing.T) { store := &imageTaskMemoryStore{} svc := NewImageTaskServiceWithOptions(store, time.Hour, time.Minute) created, err := svc.Create(context.Background(), ImageTaskOwner{UserID: 1, APIKeyID: 2}) require.NoError(t, err) require.NoError(t, svc.Complete(context.Background(), created.ID, http.StatusOK, json.RawMessage(`not-json`))) got, err := svc.Get(context.Background(), ImageTaskOwner{UserID: 1, APIKeyID: 2}, created.ID) require.NoError(t, err) require.Equal(t, ImageTaskStatusFailed, got.Status) require.Equal(t, http.StatusBadGateway, got.HTTPStatus) require.Contains(t, string(got.Error), "non-JSON") } func TestImageTaskServiceMapsStoreFailures(t *testing.T) { store := &imageTaskMemoryStore{saveErr: errors.New("redis down")} svc := NewImageTaskService(store) _, err := svc.Create(context.Background(), ImageTaskOwner{UserID: 1, APIKeyID: 2}) require.ErrorIs(t, err, ErrImageTaskUnavailable) }