feat(agent): 云端 Agent - 增加上下文预算、任务事实与终态恢复保障
This commit is contained in:
107
backend/internal/app/task_execution_contract_test.go
Normal file
107
backend/internal/app/task_execution_contract_test.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"infinite-canvas/backend/internal/model"
|
||||
)
|
||||
|
||||
func TestAgentMediaAdmissionPersistsOriginAndChargeAuthorization(t *testing.T) {
|
||||
s, _, args := agentMediaFixture(t)
|
||||
run, state := agentMediaRun(t, s, args, "auto")
|
||||
req, _, err := s.prepareCloudAgentMedia(run, &state, agentMediaCall(args))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.admission = &taskAdmission{ID: "generation-task", MaxCharge: 1000, AgentRunID: run.ID, GenerationID: "generation", ApprovalID: "approval"}
|
||||
req.creationPrepare = &creationTaskPreparation{}
|
||||
task, err := s.CreateTask(run.UserID, req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
order := req.creationPrepare.Order
|
||||
if task.AgentRunID != run.ID || task.GenerationID != "generation" || task.ApprovalID != "approval" || order == nil || !order.ChargeLimitSet || task.AuthorizedChargeMicrocredits != order.AmountMicrocredits || order.ChargeLimitMicrocredits != order.AmountMicrocredits {
|
||||
t.Fatalf("incomplete generation authorization: task=%+v order=%+v", task, order)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgentMediaRetryRequiresNewGenerationAuthorization(t *testing.T) {
|
||||
s, db, _ := agentMediaFixture(t)
|
||||
for _, task := range []model.Task{
|
||||
{ID: "agent-run-only", AgentRunID: "run"},
|
||||
{ID: "generation-only", GenerationID: "generation"},
|
||||
{ID: "operation-prefix", Operation: "cloud_agent_media"},
|
||||
{ID: "source", InputJSON: `{"metadata":{"source":"cloud_agent"}}`},
|
||||
} {
|
||||
task.UserID, task.Type, task.Status = "user", "canvas_video", model.TaskStatusFailed
|
||||
if task.Operation == "" {
|
||||
task.Operation = "image_to_video"
|
||||
}
|
||||
if task.InputJSON == "" {
|
||||
task.InputJSON = `{}`
|
||||
}
|
||||
if err := db.Create(&task).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := s.RetryTask(task.UserID, task.ID); err == nil || !strings.Contains(err.Error(), "Agent 重试") {
|
||||
t.Fatalf("media retry retained old authorization: %v", err)
|
||||
}
|
||||
stored, err := s.repo.Task(task.ID)
|
||||
if err != nil || stored.Status != model.TaskStatusFailed || stored.BillingOrderID != "" {
|
||||
t.Fatalf("retry changed old execution: %+v %v", stored, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrdinaryFailedAndCancelledTasksKeepRetryIdentity(t *testing.T) {
|
||||
s, db, _ := agentMediaFixture(t)
|
||||
for _, status := range []model.TaskStatus{model.TaskStatusFailed, model.TaskStatusCancelled} {
|
||||
task := model.Task{
|
||||
ID: "ordinary-" + string(status),
|
||||
UserID: "user",
|
||||
Type: "canvas_text",
|
||||
Operation: "text",
|
||||
Status: status,
|
||||
InputJSON: `{}`,
|
||||
RouteRun: 3,
|
||||
}
|
||||
if err := db.Create(&task).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
retried, err := s.RetryTask(task.UserID, task.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("ordinary %s task should remain retryable: %v", status, err)
|
||||
}
|
||||
if retried.ID != task.ID || retried.Status != model.TaskStatusQueued || retried.RouteRun != 4 || retried.GenerationID != "" || retried.AgentRunID != "" {
|
||||
t.Fatalf("retry must reuse task execution identity without inventing an Agent generation: %+v", retried)
|
||||
}
|
||||
if _, err = s.RetryTask(task.UserID, task.ID); err == nil {
|
||||
t.Fatal("queued retry was accepted a second time")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskCancellationPersistsFirstSourceAndDiagnostic(t *testing.T) {
|
||||
s, db, _ := agentMediaFixture(t)
|
||||
task := model.Task{ID: "cancel-child", UserID: "user", AgentRunID: "parent", Status: model.TaskStatusQueued}
|
||||
if err := db.Create(&task).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := s.taskLifecycle().cancelTaskWithIntent(context.Background(), "user", task.ID, model.TaskCancellationIntent{Source: model.TaskCancellationParentFailed}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := s.CancelTask(context.Background(), "user", task.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stored, err := s.repo.Task(task.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
diagnostic := taskExecutionDiagnostic(stored)
|
||||
if stored.CancellationSource != model.TaskCancellationParentFailed || stored.CancellationRequestedAt == nil || diagnostic == nil || diagnostic.Phase != "cancellation" || diagnostic.SubmissionOutcome != "not_submitted" || diagnostic.TaskSubmitted {
|
||||
t.Fatalf("cancellation lost facts: %+v %+v", stored, diagnostic)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user