feat: 数据概览/操作日志/批量操作 + 首页合格档案数
后台新增「数据概览」(商品/合格/按状态/品牌/分类/待审核) 与「操作日志」(全局审计分页);商品列表支持多选批量改状态/分类。公开首页标题改为「天工」并展示合格档案数;新增公开接口 /api/v1/stats 与后台 /api/stats、/api/audit、/api/products/bulk。合格口径=quality_score≥0.6 且在用。 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package adminstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStatsAndBulk(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
ctx := context.Background()
|
||||
|
||||
base, err := s.Stats(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("stats: %v", err)
|
||||
}
|
||||
|
||||
var p1, p2 string
|
||||
if err := s.pool.QueryRow(ctx,
|
||||
"INSERT INTO product (name, status) VALUES ($1,'active') RETURNING id",
|
||||
"批量测试1 "+randomHex(4)).Scan(&p1); err != nil {
|
||||
t.Fatalf("insert p1: %v", err)
|
||||
}
|
||||
if err := s.pool.QueryRow(ctx,
|
||||
"INSERT INTO product (name, status) VALUES ($1,'active') RETURNING id",
|
||||
"批量测试2 "+randomHex(4)).Scan(&p2); err != nil {
|
||||
t.Fatalf("insert p2: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { _, _ = s.pool.Exec(ctx, "DELETE FROM product WHERE id = ANY($1)", []string{p1, p2}) })
|
||||
|
||||
after, err := s.Stats(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("stats after: %v", err)
|
||||
}
|
||||
if after.Products != base.Products+2 {
|
||||
t.Fatalf("product count: got %d want %d", after.Products, base.Products+2)
|
||||
}
|
||||
|
||||
// Bulk set status to deprecated.
|
||||
n, err := s.BulkSetStatus(ctx, "tester", []string{p1, p2}, "deprecated")
|
||||
if err != nil || n != 2 {
|
||||
t.Fatalf("bulk status: n=%d err=%v", n, err)
|
||||
}
|
||||
var deprecated int
|
||||
if err := s.pool.QueryRow(ctx,
|
||||
"SELECT count(*) FROM product WHERE id = ANY($1) AND status='deprecated'",
|
||||
[]string{p1, p2}).Scan(&deprecated); err != nil {
|
||||
t.Fatalf("verify: %v", err)
|
||||
}
|
||||
if deprecated != 2 {
|
||||
t.Fatalf("expected 2 deprecated, got %d", deprecated)
|
||||
}
|
||||
|
||||
// Invalid status rejected.
|
||||
if _, err := s.BulkSetStatus(ctx, "tester", []string{p1}, "nope"); err != ErrInvalidStatus {
|
||||
t.Fatalf("expected ErrInvalidStatus, got %v", err)
|
||||
}
|
||||
|
||||
// Empty selection rejected.
|
||||
if _, err := s.BulkSetStatus(ctx, "tester", nil, "active"); err != ErrNoTargets {
|
||||
t.Fatalf("expected ErrNoTargets, got %v", err)
|
||||
}
|
||||
|
||||
// Global audit log should contain the bulk_status entry.
|
||||
rows, total, err := s.ListAllAudit(ctx, 10, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("audit: %v", err)
|
||||
}
|
||||
if total == 0 || len(rows) == 0 {
|
||||
t.Fatalf("expected audit rows, got total=%d", total)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user