Files
goauto/server/app/goauto/purchase/admin_query_test.go
T

212 lines
8.3 KiB
Go

package purchase
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"go-admin/app/goauto/models"
"go-admin/app/goauto/purchasecontract"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
func createAdminQueryTask(t *testing.T, s *Service, f fixture) models.PurchaseTask {
t.Helper()
rule := json.RawMessage(`{"schemaVersion":1,"ruleType":"pddPurchase","requiredCapabilities":["purchase.rehearsal.v1"],"actions":[{"type":"openProduct"},{"type":"verifyProduct"},{"type":"verifyOrderSummary"}]}`)
task, _, err := s.Create(context.Background(), CreateRequest{
RequestID: uuid.NewString(), ExecutionMode: models.PurchaseExecutionModeRehearsal,
PDDProductID: &f.pdd.ID, DeviceID: &f.device.ID, Quantity: 1,
MinUnitPriceCent: 100, MaxUnitPriceCent: 5000, Currency: "CNY", RuleSnapshot: rule,
})
if err != nil {
t.Fatalf("create purchase task: %v", err)
}
return task
}
func TestAdminListAndDetailExposeSafePurchaseViews(t *testing.T) {
db := testDB(t)
f := seed(t, db, []string{purchasecontract.CapabilityPurchaseRehearsalV1}, true)
s := testService(db)
task := createAdminQueryTask(t, s, f)
attempt := models.PurchaseTaskAttempt{
TaskID: task.ID, AttemptID: uuid.NewString(), AttemptNumber: 1,
Phase: models.PurchaseAttemptPhasePurchase, Status: models.PurchaseAttemptStatusPending,
DeviceID: &f.device.ID, RuleSnapshotHash: strings.Repeat("a", 64), SpecDecisionSnapshot: `{}`,
}
if err := db.Create(&attempt).Error; err != nil {
t.Fatalf("create attempt: %v", err)
}
list, err := s.AdminList(context.Background(), AdminListRequest{
Page: 1, PageSize: 20, TaskID: task.ID, Status: models.PurchaseTaskStatusPending,
ExecutionMode: models.PurchaseExecutionModeRehearsal,
})
if err != nil {
t.Fatalf("admin list: %v", err)
}
if list.Total != 1 || len(list.Items) != 1 || list.Items[0].DeviceName != f.device.Name || list.Items[0].PDDGoodsIDSnapshot != f.pdd.GoodsID {
t.Fatalf("unexpected list: %+v", list)
}
detail, err := s.AdminDetail(context.Background(), task.ID)
if err != nil {
t.Fatalf("admin detail: %v", err)
}
if detail.Task.ID != task.ID || len(detail.Attempts) != 1 || detail.Attempts[0].AttemptID != attempt.AttemptID {
t.Fatalf("unexpected detail: %+v", detail)
}
raw, err := json.Marshal(detail)
if err != nil {
t.Fatal(err)
}
for _, forbidden := range []string{"ruleSnapshot\"", "requiredCapabilities", "createRequestId", "token", "credential", "shippingAddress", "accessibilityTree", "screenshot"} {
if strings.Contains(string(raw), forbidden) {
t.Fatalf("admin detail leaked %q: %s", forbidden, raw)
}
}
}
func TestAdminListFiltersByShopeeOrderNumberSnapshot(t *testing.T) {
db := testDB(t)
f := seed(t, db, liveCaps(), true)
s := testService(db)
task, err := createLive(t, s, f)
if err != nil {
t.Fatalf("create live task: %v", err)
}
list, err := s.AdminList(context.Background(), AdminListRequest{Page: 1, PageSize: 20, ShopeeOrderNo: "YB-"})
if err != nil {
t.Fatalf("admin list: %v", err)
}
if list.Total != 1 || len(list.Items) != 1 || list.Items[0].ID != task.ID || list.Items[0].ShopeeOrderNoSnapshot != f.syb.OrderCode {
t.Fatalf("unexpected order query result: %+v", list)
}
empty, err := s.AdminList(context.Background(), AdminListRequest{Page: 1, PageSize: 20, ShopeeOrderNo: "NOT-FOUND"})
if err != nil || empty.Total != 0 {
t.Fatalf("unexpected empty result: %+v %v", empty, err)
}
}
func TestAdminQueryValidationAndNotFound(t *testing.T) {
s := testService(testDB(t))
if _, err := s.AdminList(context.Background(), AdminListRequest{Status: "unknown"}); code(err) != CodeInvalidRequest {
t.Fatalf("invalid status accepted: %v", err)
}
if _, err := s.AdminList(context.Background(), AdminListRequest{ExecutionMode: "unknown"}); code(err) != CodeInvalidRequest {
t.Fatalf("invalid mode accepted: %v", err)
}
if _, err := s.AdminDetail(context.Background(), 999); code(err) != CodeTaskNotFound {
t.Fatalf("missing task error=%v", err)
}
}
func TestAdminListSYBWritebackSucceededFilter(t *testing.T) {
s, first := orderWritebackFixture(t)
db := s.DB
if err := db.Model(&models.PurchaseOrderWriteback{}).Where("purchase_task_id = ?", first.ID).Update("status", "succeeded").Error; err != nil {
t.Fatal(err)
}
var successIDs []uint64
successIDs = append(successIDs, first.ID)
for _, state := range []string{"succeeded", "pending", "running", "failed", "unknown", "conflict", "none"} {
task := first
task.ID = 0
task.Status = models.PurchaseTaskStatusCancelled
task.PDDOrderNo = nil
task.WritebackStatus = models.PurchaseWritebackStatusSucceeded // old logistics must not match
task.CreateRequestID = uuid.NewString()
task.UnknownResolveRequestID = nil
if err := db.Create(&task).Error; err != nil {
t.Fatal(err)
}
if state != "none" {
if err := db.Create(&models.PurchaseOrderWriteback{PurchaseTaskID: task.ID, StockID: 2, DetailID: 1, OrderNo: "FILTER-DEMO", Status: state}).Error; err != nil {
t.Fatal(err)
}
}
if state == "succeeded" {
successIDs = append(successIDs, task.ID)
}
}
for page := 1; page <= 2; page++ {
out, err := s.AdminList(context.Background(), AdminListRequest{Status: adminStatusSYBWritebackSucceeded, Page: page, PageSize: 1})
if err != nil || out.Total != 2 || len(out.Items) != 1 || out.Items[0].ID != successIDs[2-page] {
t.Fatalf("page %d: %+v, %v", page, out, err)
}
}
for _, req := range []AdminListRequest{
{Status: adminStatusSYBWritebackSucceeded, TaskID: first.ID},
{Status: models.PurchaseTaskStatusOrderCreated},
{Status: adminStatusSYBWritebackSucceeded, PDDOrderNo: *first.PDDOrderNo, ExecutionMode: "live", TaskType: "syb_order", SYBProductID: *first.SYBProductID},
} {
out, err := s.AdminList(context.Background(), req)
if err != nil || out.Total != 1 || len(out.Items) != 1 || out.Items[0].ID != first.ID {
t.Fatalf("combined filter: %+v %v", out, err)
}
}
out, err := s.AdminList(context.Background(), AdminListRequest{})
if err != nil || out.Total != 8 {
t.Fatalf("clear filter: %+v %v", out, err)
}
out, err = s.AdminList(context.Background(), AdminListRequest{Status: adminStatusSYBWritebackSucceeded, PDDOrderNo: "not-found"})
if err != nil || out.Total != 0 {
t.Fatalf("empty filter: %+v %v", out, err)
}
if validPurchaseStatus(adminStatusSYBWritebackSucceeded) {
t.Fatal("filter became task state")
}
}
func TestAdminQueryHandlersRequireOperatorRole(t *testing.T) {
gin.SetMode(gin.TestMode)
for _, path := range []string{"/api/admin/v1/purchase-tasks", "/api/admin/v1/purchase-tasks/1"} {
recorder := httptest.NewRecorder()
context, _ := gin.CreateTestContext(recorder)
context.Request = httptest.NewRequest(http.MethodGet, path, nil)
context.Params = gin.Params{{Key: "taskId", Value: "1"}}
if strings.HasSuffix(path, "/1") {
(Handler{}).AdminDetail(context)
} else {
(Handler{}).AdminList(context)
}
if recorder.Code != http.StatusForbidden || !strings.Contains(recorder.Body.String(), "FORBIDDEN") {
t.Fatalf("unauthorized query %s returned %d %s", path, recorder.Code, recorder.Body.String())
}
}
}
func TestAdminBatchHandlersRequireOperatorRole(t *testing.T) {
gin.SetMode(gin.TestMode)
for _, handler := range []func(*gin.Context){(Handler{}).AdminBatchPreview, (Handler{}).AdminBatchCreate, (Handler{}).AdminBatchRetry, (Handler{}).AdminOrderWriteback} {
recorder := httptest.NewRecorder()
context, _ := gin.CreateTestContext(recorder)
context.Request = httptest.NewRequest(http.MethodPost, "/api/admin/v1/purchase-tasks/batch", strings.NewReader(`{}`))
handler(context)
if recorder.Code != http.StatusForbidden || !strings.Contains(recorder.Body.String(), "FORBIDDEN") {
t.Fatalf("unauthorized batch handler returned %d %s", recorder.Code, recorder.Body.String())
}
}
}
func TestAdminSuccessEnvelopeIsCompatibleWithGoAdminUI(t *testing.T) {
gin.SetMode(gin.TestMode)
recorder := httptest.NewRecorder()
context, _ := gin.CreateTestContext(recorder)
writeAdminReplay(context, gin.H{"id": 42}, true)
if recorder.Code != http.StatusOK {
t.Fatalf("status=%d", recorder.Code)
}
var response map[string]any
if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil {
t.Fatal(err)
}
if response["code"] != float64(http.StatusOK) || response["replayed"] != true || response["data"] == nil {
t.Fatalf("unexpected admin envelope: %v", response)
}
}