feat: 按创建人隔离采集采购任务 (#127)
This commit is contained in:
+117
-17
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -39,20 +40,21 @@ func TestListTasksView_统一每页20条并收敛越界页(t *testing.T) {
|
||||
// 跟 client_test.go 里的 insertTask 不一样:那个专为"领取"场景写死了固定值,
|
||||
// 这里需要覆盖类型、状态、options 等更多维度。
|
||||
type testTaskParams struct {
|
||||
taskID string
|
||||
taskType model.TaskType
|
||||
status model.TaskStatus
|
||||
assignedClient string
|
||||
orderNo string
|
||||
pddGoodsID string
|
||||
pddOptions string
|
||||
quantity int
|
||||
maxPriceCent int64
|
||||
errorCode string
|
||||
errorMessage string
|
||||
resultData string
|
||||
claimedAt string
|
||||
finishedAt string
|
||||
taskID string
|
||||
taskType model.TaskType
|
||||
status model.TaskStatus
|
||||
assignedClient string
|
||||
orderNo string
|
||||
pddGoodsID string
|
||||
pddOptions string
|
||||
quantity int
|
||||
maxPriceCent int64
|
||||
errorCode string
|
||||
errorMessage string
|
||||
resultData string
|
||||
claimedAt string
|
||||
finishedAt string
|
||||
createdByUserID string
|
||||
}
|
||||
|
||||
func insertTestTask(t *testing.T, db *sql.DB, p testTaskParams) {
|
||||
@@ -100,20 +102,27 @@ func insertTestTask(t *testing.T, db *sql.DB, p testTaskParams) {
|
||||
INSERT INTO tasks (task_id, task_type, status, assigned_client, claimed_at,
|
||||
order_no, pdd_goods_url, pdd_goods_id, pdd_options,
|
||||
quantity, max_price_cent,
|
||||
error_code, error_message, result_data, finished_at,
|
||||
error_code, error_message, result_data, finished_at, created_by_user_id,
|
||||
created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'https://mobile.yangkeduo.com/goods.html?goods_id=1', ?, ?,
|
||||
?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
p.taskID, string(p.taskType), string(p.status), assigned, claimedAt,
|
||||
orderNo, pddGoodsID, pddOptions,
|
||||
quantity, maxPrice,
|
||||
errCode, errMsg, resultData, finishedAt,
|
||||
errCode, errMsg, resultData, finishedAt, nullableString(p.createdByUserID),
|
||||
now, now)
|
||||
if err != nil {
|
||||
t.Fatalf("插入测试任务 %s 失败: %v", p.taskID, err)
|
||||
}
|
||||
}
|
||||
|
||||
func nullableString(value string) any {
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// ── 列表:同时显示采集和采购 ──────────────────────────
|
||||
|
||||
func TestListTasksView_同时显示采集和采购(t *testing.T) {
|
||||
@@ -515,6 +524,97 @@ func TestDeleteTasks_批量删除(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskScope_采购员只能查看自己的任务且不能伪造筛选(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
admin, buyerA, buyerB := insertTaskUsers(t, db)
|
||||
insertTestTask(t, db, testTaskParams{taskID: "OWN-A", taskType: model.TaskCollect, status: model.TaskPending, createdByUserID: buyerA.UserID})
|
||||
insertTestTask(t, db, testTaskParams{taskID: "OWN-B", taskType: model.TaskCollect, status: model.TaskSucceeded, createdByUserID: buyerB.UserID})
|
||||
insertTestTask(t, db, testTaskParams{taskID: "HISTORY", taskType: model.TaskCollect, status: model.TaskFailed})
|
||||
|
||||
buyerResult, err := ListTasksViewForUser(db, buyerA, repository.TaskFilter{}, TaskCreatorHistoryValue, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if buyerResult.Total != 1 || len(buyerResult.Rows) != 1 || buyerResult.Rows[0].TaskID != "OWN-A" {
|
||||
t.Fatalf("采购员范围不正确: %+v", buyerResult.Rows)
|
||||
}
|
||||
if buyerResult.Counts[model.TaskSucceeded] != 0 || buyerResult.Counts[model.TaskFailed] != 0 {
|
||||
t.Fatalf("采购员统计泄露了其他任务: %+v", buyerResult.Counts)
|
||||
}
|
||||
|
||||
history, err := ListTasksViewForUser(db, admin, repository.TaskFilter{}, TaskCreatorHistoryValue, 1)
|
||||
if err != nil || history.Total != 1 || history.Rows[0].TaskID != "HISTORY" {
|
||||
t.Fatalf("管理员历史任务筛选不正确: err=%v rows=%+v", err, history.Rows)
|
||||
}
|
||||
byBuyer, err := ListTasksViewForUser(db, admin, repository.TaskFilter{}, buyerB.UserID, 1)
|
||||
if err != nil || byBuyer.Total != 1 || byBuyer.Rows[0].TaskID != "OWN-B" {
|
||||
t.Fatalf("管理员创建人筛选不正确: err=%v rows=%+v", err, byBuyer.Rows)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskScope_详情越权返回不存在且批量删除完整回滚(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
admin, buyerA, buyerB := insertTaskUsers(t, db)
|
||||
insertTestTask(t, db, testTaskParams{taskID: "OWN-A", taskType: model.TaskCollect, status: model.TaskPending, createdByUserID: buyerA.UserID})
|
||||
insertTestTask(t, db, testTaskParams{taskID: "OWN-B", taskType: model.TaskCollect, status: model.TaskPending, createdByUserID: buyerB.UserID})
|
||||
|
||||
detail, err := GetTaskDetailForUser(db, buyerA, "OWN-B")
|
||||
if err != nil || detail != nil {
|
||||
t.Fatalf("越权详情应表现为不存在: detail=%+v err=%v", detail, err)
|
||||
}
|
||||
if _, err := DeleteTasksForUser(db, buyerA, []string{"OWN-A", "OWN-B"}); !errors.Is(err, ErrTaskNotVisible) {
|
||||
t.Fatalf("混合越权删除应整批拒绝: %v", err)
|
||||
}
|
||||
for _, id := range []string{"OWN-A", "OWN-B"} {
|
||||
visible, err := repository.TaskVisibleToUser(db, id, "")
|
||||
if err != nil || !visible {
|
||||
t.Fatalf("回滚后任务 %s 应仍存在: visible=%t err=%v", id, visible, err)
|
||||
}
|
||||
}
|
||||
if n, err := DeleteTasksForUser(db, buyerA, []string{"OWN-A"}); err != nil || n != 1 {
|
||||
t.Fatalf("采购员删除本人任务失败: n=%d err=%v", n, err)
|
||||
}
|
||||
if n, err := DeleteTasksForUser(db, admin, []string{"OWN-B"}); err != nil || n != 1 {
|
||||
t.Fatalf("管理员删除任意任务失败: n=%d err=%v", n, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskCreatorOptions_包含禁用账号和历史任务(t *testing.T) {
|
||||
db := newTestDB(t)
|
||||
admin, _, buyerB := insertTaskUsers(t, db)
|
||||
if _, err := db.Exec(`UPDATE users SET status='disabled' WHERE user_id=?`, buyerB.UserID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
options, err := TaskCreatorOptions(db, admin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
texts := map[string]string{}
|
||||
for _, option := range options {
|
||||
texts[option.Value] = option.Text
|
||||
}
|
||||
if texts[TaskCreatorHistoryValue] != "历史任务" || texts[buyerB.UserID] != buyerB.Username+"(已禁用)" {
|
||||
t.Fatalf("创建人选项不完整: %+v", texts)
|
||||
}
|
||||
}
|
||||
|
||||
func insertTaskUsers(t *testing.T, db *sql.DB) (*model.User, *model.User, *model.User) {
|
||||
t.Helper()
|
||||
now := model.NowISO()
|
||||
users := []*model.User{
|
||||
{UserID: "task-admin", Username: "task-admin", PasswordHash: "hash", Role: model.RoleAdmin, Status: model.UserActive},
|
||||
{UserID: "task-buyer-a", Username: "task-buyer-a", PasswordHash: "hash", Role: model.RolePurchaser, Status: model.UserActive},
|
||||
{UserID: "task-buyer-b", Username: "task-buyer-b", PasswordHash: "hash", Role: model.RolePurchaser, Status: model.UserActive},
|
||||
}
|
||||
for _, user := range users {
|
||||
user.PasswordChangedAt, user.CreatedAt, user.UpdatedAt = now, now, now
|
||||
if err := repository.CreateUser(db, *user); err != nil {
|
||||
t.Fatalf("插入任务测试用户失败: %v", err)
|
||||
}
|
||||
}
|
||||
return users[0], users[1], users[2]
|
||||
}
|
||||
|
||||
// ── 目标列拼接的边界情况 ──────────────────────────────
|
||||
|
||||
func TestSpecText_空或无法解析时返回占位符(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user