feat(t215): add admin order authorization

This commit is contained in:
QiuSW
2026-07-28 12:50:42 +08:00
parent 16c2ea410a
commit 827afc7257
24 changed files with 2688 additions and 113 deletions
@@ -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)