feat(t206): connect Android procurement tasks

This commit is contained in:
QiuSW
2026-07-27 11:11:16 +08:00
parent 9e698c1b6c
commit 45b66eeea7
35 changed files with 2634 additions and 81 deletions
@@ -5,6 +5,7 @@ import (
"net/http"
"strconv"
"strings"
"time"
"cmroubao/backend-api/internal/domain"
"cmroubao/backend-api/internal/transport/authcommon"
@@ -235,8 +236,11 @@ func (handler *deviceHandlers) startTask(ctx *gin.Context) {
}
ctx.Header("Cache-Control", "no-store")
ctx.JSON(http.StatusOK, gin.H{
"task": deviceTaskResponse(result.Task),
"execution": executionResponse(result.Execution),
"task": deviceTaskResponse(result.Task),
"execution": deviceExecutionResponse(
result.Execution,
result.Task.ClaimExpiresAt,
),
"replayed": result.Replayed,
"server_time": formatTime(result.ServerTime),
})
@@ -275,8 +279,11 @@ func (handler *deviceHandlers) heartbeatTask(ctx *gin.Context) {
}
ctx.Header("Cache-Control", "no-store")
ctx.JSON(http.StatusOK, gin.H{
"task": deviceTaskResponse(result.Task),
"execution": executionResponse(result.Execution),
"task": deviceTaskResponse(result.Task),
"execution": deviceExecutionResponse(
result.Execution,
result.Task.ClaimExpiresAt,
),
"cancel_requested": result.CancelRequested,
"server_time": formatTime(result.ServerTime),
})
@@ -453,3 +460,12 @@ func deviceTaskResponse(task domain.PurchaseTask) gin.H {
"currency": task.Currency,
}
}
func deviceExecutionResponse(
execution domain.TaskExecution,
expiresAt *time.Time,
) gin.H {
response := executionResponse(execution)
response["execution_expires_at"] = formatOptionalTime(expiresAt)
return response
}
@@ -432,9 +432,14 @@ func TestDeviceStartAndTaskHeartbeatUseClaimContract(t *testing.T) {
if started.Task.Status != string(domain.TaskStatusRunning) ||
started.Execution.ID == "" ||
started.Execution.CurrentStep != "PREFLIGHT" ||
started.Execution.ExpiresAt.IsZero() ||
started.Execution.OrderSubmitted {
t.Fatalf("start response = %+v", started)
}
if remaining := time.Until(started.Execution.ExpiresAt); remaining < 9*time.Minute ||
remaining > 11*time.Minute {
t.Fatalf("start execution expiry remaining = %s", remaining)
}
assertNoClaimSecret(t, startedResponse, testOpaqueToken)
heartbeatBody := fmt.Sprintf(
@@ -464,6 +469,8 @@ func TestDeviceStartAndTaskHeartbeatUseClaimContract(t *testing.T) {
if heartbeat.Task.Status != string(domain.TaskStatusRunning) ||
heartbeat.Execution.ID != started.Execution.ID ||
heartbeat.Execution.CurrentStep != "SEARCH_RESULTS" ||
heartbeat.Execution.ExpiresAt.IsZero() ||
heartbeat.Execution.ExpiresAt.Before(started.Execution.ExpiresAt) ||
heartbeat.Execution.OrderSubmitted ||
heartbeat.CancelRequested {
t.Fatalf("task heartbeat response = %+v", heartbeat)
@@ -918,9 +925,10 @@ type deviceLifecycleResponse struct {
ReferenceImageURL string `json:"reference_image_url"`
} `json:"task"`
Execution struct {
ID string `json:"id"`
CurrentStep string `json:"current_step"`
OrderSubmitted bool `json:"order_submitted"`
ID string `json:"id"`
CurrentStep string `json:"current_step"`
OrderSubmitted bool `json:"order_submitted"`
ExpiresAt time.Time `json:"execution_expires_at"`
} `json:"execution"`
Replayed bool `json:"replayed"`
CancelRequested bool `json:"cancel_requested"`