feat(t215): add admin order authorization
This commit is contained in:
@@ -509,6 +509,124 @@ func TestTaskDetailDoesNotLeakForbiddenResource(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskDetailAuthorizesOneCandidateWithCSRFAndPRG(t *testing.T) {
|
||||
firstKey := strings.Repeat("a", 64)
|
||||
secondKey := strings.Repeat("b", 64)
|
||||
service := &fakeService{
|
||||
getResult: Task{
|
||||
ID: testTaskID,
|
||||
Title: "候选确认任务",
|
||||
SKU: "BLACK-L",
|
||||
Quantity: 2,
|
||||
Status: "WAITING_CONFIRMATION",
|
||||
Version: 7,
|
||||
ReferenceAssetID: "00000000-0000-4000-8000-000000000009",
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
TaskContentSHA256: strings.Repeat("c", 64),
|
||||
Candidates: []AuthorizationCandidate{
|
||||
{
|
||||
CandidateKey: firstKey,
|
||||
Ordinal: 1,
|
||||
Title: "候选一",
|
||||
SKUText: "BLACK-L",
|
||||
PriceText: "20.00",
|
||||
EvidenceURLs: []string{"/api/v1/assets/evidence-1/content"},
|
||||
},
|
||||
{
|
||||
CandidateKey: secondKey,
|
||||
Ordinal: 2,
|
||||
Title: "候选二",
|
||||
SKUText: "BLACK-XL",
|
||||
PriceText: "22.00",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
router := newTestRouter(t, service)
|
||||
detail := performRequest(
|
||||
t,
|
||||
router,
|
||||
http.MethodGet,
|
||||
"/tasks/"+testTaskID,
|
||||
nil,
|
||||
"",
|
||||
)
|
||||
if detail.Code != http.StatusOK {
|
||||
t.Fatalf("detail status/body = %d/%s", detail.Code, detail.Body)
|
||||
}
|
||||
for _, expected := range []string{
|
||||
"候选确认与下单授权",
|
||||
"系统只创建待付款订单",
|
||||
"候选一",
|
||||
"BLACK-XL",
|
||||
`name="candidate_key"`,
|
||||
} {
|
||||
if !strings.Contains(detail.Body.String(), expected) {
|
||||
t.Fatalf("authorization detail missing %q", expected)
|
||||
}
|
||||
}
|
||||
cookie := csrfCookie(t, detail)
|
||||
authorizationKey := hiddenValue(
|
||||
t,
|
||||
detail.Body.String(),
|
||||
"authorization_key",
|
||||
)
|
||||
form := url.Values{
|
||||
"csrf_token": {cookie.Value},
|
||||
"authorization_key": {authorizationKey},
|
||||
"expected_task_version": {"7"},
|
||||
"candidate_key": {firstKey},
|
||||
"selected_reason_code": {"SKU_MATCH"},
|
||||
"rejected_reason_code": {"NOT_BEST_MATCH"},
|
||||
"authorization_note": {"已核对图片、规格与价格"},
|
||||
"supersedes_authorization_id": {""},
|
||||
}
|
||||
request := httptest.NewRequest(
|
||||
http.MethodPost,
|
||||
"/tasks/"+testTaskID+"/order-authorizations",
|
||||
strings.NewReader(form.Encode()),
|
||||
)
|
||||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
request.AddCookie(cookie)
|
||||
response := httptest.NewRecorder()
|
||||
router.ServeHTTP(response, request)
|
||||
|
||||
if response.Code != http.StatusSeeOther ||
|
||||
response.Header().Get("Location") !=
|
||||
"/tasks/"+testTaskID+"?notice=authorization-created" {
|
||||
t.Fatalf(
|
||||
"status/location = %d/%q",
|
||||
response.Code,
|
||||
response.Header().Get("Location"),
|
||||
)
|
||||
}
|
||||
if service.authorizeInput.TaskID != testTaskID ||
|
||||
service.authorizeInput.IdempotencyKey != authorizationKey ||
|
||||
service.authorizeInput.ExpectedTaskVersion != 7 ||
|
||||
service.authorizeInput.CandidateKey != firstKey ||
|
||||
service.authorizeInput.SelectedReasonCode != "SKU_MATCH" ||
|
||||
service.authorizeInput.RejectedReasonCode != "NOT_BEST_MATCH" {
|
||||
t.Fatalf("authorize input = %+v", service.authorizeInput)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskDetailDisablesAuthorizationAfterDelivery(t *testing.T) {
|
||||
view := taskDetailViewFrom(Task{
|
||||
Status: "WAITING_CONFIRMATION",
|
||||
Candidates: []AuthorizationCandidate{{
|
||||
CandidateKey: strings.Repeat("a", 64),
|
||||
}},
|
||||
OrderAuthorizations: []OrderAuthorization{{
|
||||
ID: "00000000-0000-4000-8000-000000000010",
|
||||
Status: "DELIVERED",
|
||||
}},
|
||||
})
|
||||
if view.CanAuthorizeOrder {
|
||||
t.Fatal("delivered authorization remains editable")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskDetailCancelModeFollowsLifecycleStatus(t *testing.T) {
|
||||
tests := []struct {
|
||||
status string
|
||||
@@ -614,23 +732,26 @@ func TestRendererUsesMissingKeyErrors(t *testing.T) {
|
||||
}
|
||||
|
||||
type fakeService struct {
|
||||
listInput ListTasksInput
|
||||
listResult TaskList
|
||||
listErr error
|
||||
getResult Task
|
||||
getErr error
|
||||
uploadResult UploadedAsset
|
||||
uploadErr error
|
||||
uploadInput UploadReferenceInput
|
||||
uploadBody []byte
|
||||
uploadCalls int
|
||||
createResult Task
|
||||
createErr error
|
||||
createInput CreateTaskInput
|
||||
createCalls int
|
||||
cancelResult Task
|
||||
cancelErr error
|
||||
cancelInput CancelTaskInput
|
||||
listInput ListTasksInput
|
||||
listResult TaskList
|
||||
listErr error
|
||||
getResult Task
|
||||
getErr error
|
||||
uploadResult UploadedAsset
|
||||
uploadErr error
|
||||
uploadInput UploadReferenceInput
|
||||
uploadBody []byte
|
||||
uploadCalls int
|
||||
createResult Task
|
||||
createErr error
|
||||
createInput CreateTaskInput
|
||||
createCalls int
|
||||
cancelResult Task
|
||||
cancelErr error
|
||||
cancelInput CancelTaskInput
|
||||
authorizeResult OrderAuthorization
|
||||
authorizeErr error
|
||||
authorizeInput AuthorizeOrderInput
|
||||
}
|
||||
|
||||
func (service *fakeService) ListTasks(
|
||||
@@ -679,6 +800,14 @@ func (service *fakeService) CancelTask(
|
||||
return service.cancelResult, service.cancelErr
|
||||
}
|
||||
|
||||
func (service *fakeService) AuthorizeOrder(
|
||||
_ context.Context,
|
||||
input AuthorizeOrderInput,
|
||||
) (OrderAuthorization, error) {
|
||||
service.authorizeInput = input
|
||||
return service.authorizeResult, service.authorizeErr
|
||||
}
|
||||
|
||||
func newTestRouter(t *testing.T, service Service) http.Handler {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
Reference in New Issue
Block a user