108 lines
5.6 KiB
Go
108 lines
5.6 KiB
Go
package jobs
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.nianxx.cn/wangxuming/NianAIGC/backend/internal/providers"
|
|
)
|
|
|
|
func TestAllProviderRejectionsPersistSafeReasonAndNeverResubmit(t *testing.T) {
|
|
for _, provider := range []string{"seedance", "seedream", "bailian", "minimax", "evolink", "volcengine-visual"} {
|
|
t.Run(provider, func(t *testing.T) {
|
|
store := newMemoryJobStore()
|
|
job := failureFixture(provider)
|
|
store.jobs[job.ID] = job
|
|
adapter := &countingProvider{err: &providers.ProviderError{Operation: provider + " submit", Status: 400,
|
|
Code: "InputImageSensitiveContentDetected.PrivacyInformation", RequestID: "request-123",
|
|
Detail: "private prompt rejected https://private.test/input.png?token=secret Bearer secret-key"}}
|
|
processor := ProviderProcessor{Providers: ProviderRegistry{provider: adapter}, Store: store}
|
|
got, err := processor.Advance(context.Background(), job)
|
|
if err != nil || got.Status != StatusFailed || got.Error == nil || got.Error.Retryable || got.Error.Message == unknownProviderSubmissionMessage {
|
|
t.Fatalf("rejection=%#v err=%v", got, err)
|
|
}
|
|
if provider == "seedance" && !strings.Contains(got.Error.Message, "真人") {
|
|
t.Fatalf("missing portrait reason: %s", got.Error.Message)
|
|
}
|
|
if !strings.Contains(string(got.ResponsePayload), "request-123") || !strings.Contains(string(got.ResponsePayload), "PrivacyInformation") {
|
|
t.Fatalf("missing diagnostic: %s", got.ResponsePayload)
|
|
}
|
|
for _, private := range []string{"private prompt", "https://private.test", "secret-key", "token=secret"} {
|
|
if strings.Contains(string(got.ResponsePayload), private) || strings.Contains(got.Error.Message, private) {
|
|
t.Fatalf("leaked %s in failure", private)
|
|
}
|
|
}
|
|
if _, err := processor.Advance(context.Background(), got); err != nil || adapter.submits != 1 {
|
|
t.Fatalf("refused request resubmitted: %d, %v", adapter.submits, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTerminalProviderFailureRetainsTaskAndDiscardsRawPayload(t *testing.T) {
|
|
store := newMemoryJobStore()
|
|
job := failureFixture("seedance")
|
|
job.ProviderTaskID = "existing-task"
|
|
store.jobs[job.ID] = job
|
|
adapter := &countingProvider{result: providers.Result{Status: providers.StatusFailed,
|
|
ErrorCode: "InputImageSensitiveContentDetected.PrivacyInformation", RequestID: "request-terminal",
|
|
ErrorMessage: "private prompt rejected", Raw: json.RawMessage(`{"error":{"message":"private prompt rejected","secret":"raw-secret"}}`)}}
|
|
processor := ProviderProcessor{Providers: ProviderRegistry{"seedance": adapter}, Store: store}
|
|
got, err := processor.Advance(context.Background(), job)
|
|
if err != nil || got.ProviderTaskID != "existing-task" || got.Error == nil || !strings.Contains(got.Error.Message, "真人") || got.Error.Retryable || adapter.submits != 0 {
|
|
t.Fatalf("terminal failure=%#v err=%v", got, err)
|
|
}
|
|
if strings.Contains(string(got.ResponsePayload), "private prompt") || strings.Contains(string(got.ResponsePayload), "raw-secret") || strings.Contains(string(got.ResponsePayload), `"raw"`) {
|
|
t.Fatalf("persisted unsafe raw error: %s", got.ResponsePayload)
|
|
}
|
|
}
|
|
|
|
func TestUnknownSubmitRemainsNonRetryable(t *testing.T) {
|
|
store := newMemoryJobStore()
|
|
job := failureFixture("seedance")
|
|
store.jobs[job.ID] = job
|
|
adapter := &countingProvider{err: &providers.ProviderError{Operation: "seedance submit", Cause: context.DeadlineExceeded}}
|
|
processor := ProviderProcessor{Providers: ProviderRegistry{"seedance": adapter}, Store: store}
|
|
got, err := processor.Advance(context.Background(), job)
|
|
if err != nil || got.Error == nil || got.Error.Message != unknownProviderSubmissionMessage || got.Error.Retryable {
|
|
t.Fatalf("uncertain submission=%#v err=%v", got, err)
|
|
}
|
|
if _, err := processor.Advance(context.Background(), got); err != nil || adapter.submits != 1 {
|
|
t.Fatalf("uncertain request resubmitted: %d, %v", adapter.submits, err)
|
|
}
|
|
}
|
|
|
|
func TestTransientQueryFailurePreservesTaskAndRecoversWithoutSubmit(t *testing.T) {
|
|
store := newMemoryJobStore()
|
|
job := failureFixture("bailian")
|
|
job.ProviderTaskID = "existing-task"
|
|
store.jobs[job.ID] = job
|
|
adapter := &countingProvider{err: &providers.ProviderError{Operation: "bailian query", Status: 503, RequestID: "query-123"}}
|
|
processor := ProviderProcessor{Providers: ProviderRegistry{"bailian": adapter}, Store: store}
|
|
failed, err := processor.Advance(context.Background(), job)
|
|
if err != nil || failed.Error == nil || !failed.Error.Retryable || failed.ProviderTaskID != job.ProviderTaskID {
|
|
t.Fatalf("query failure=%#v err=%v", failed, err)
|
|
}
|
|
worker := NewWorker(store, processor, nil, nil, nil, WorkerConfig{}, nil)
|
|
retry, action, err := worker.settle(context.Background(), failed)
|
|
if err != nil || action != "retry_scheduled" || retry.ProviderTaskID != "existing-task" {
|
|
t.Fatalf("retry=%#v action=%s err=%v", retry, action, err)
|
|
}
|
|
adapter.err = nil
|
|
adapter.result = providers.Result{Status: providers.StatusSucceeded, OutputURLs: []string{"https://result.test/image.png"}}
|
|
done, err := processor.Advance(context.Background(), retry)
|
|
if err != nil || done.Status != StatusSucceeded || done.Error != nil || done.ProviderTaskID != "existing-task" || adapter.submits != 0 {
|
|
t.Fatalf("recovery=%#v err=%v submits=%d", done, err, adapter.submits)
|
|
}
|
|
if strings.Contains(string(done.ResponsePayload), "providerError") {
|
|
t.Fatalf("stale failure after success: %s", done.ResponsePayload)
|
|
}
|
|
}
|
|
|
|
func failureFixture(provider string) Job {
|
|
return Job{ID: "job-failure", OwnerID: "owner", Provider: provider, Capability: "video.generate", Status: StatusQueued,
|
|
RequestPayload: json.RawMessage(`{"capability":"video.generate","prompt":"private prompt","inputUrls":["https://private.test/input.png?token=secret"]}`)}
|
|
}
|