212 lines
8.3 KiB
Go
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)
|
|
}
|
|
}
|