test: 完善方案一业务逻辑测试和方案二规模测试

方案一(业务逻辑正确性测试):
- 修复 SocialAccount.CreatedAt/UpdatedAt NULL 扫描问题(改为 *time.Time 指针)
- 修复 OPLOG_003~006 数据隔离(改用唯一前缀+Search方法隔离)
- 修复 DEV_008 设备列表测试(改用UserID过滤器隔离)
- 修复并发测试 cache=private → cache=shared(SQLite连接共享)
- 新增 testEnv 隔离架构(独立DB + 独立 httptest.Server)

方案二(真实数据规模测试):
- 新增 LatencyStats P99/P95 百分位统计采集器
- 全部 16 个测试迁移至 newIsolatedDB(独立内存DB,WAL模式)
- 关键查询添加 P99 多次采样统计(UL/LL/DV/DS/PR/AUTH/OPLOG)
- 新增 CONC_SCALE_001~003 并发压测(50-100 goroutine)
- 删除旧 setupScaleTestDB 死代码
- 双阈值体系:SQLite本地宽松阈值 vs PostgreSQL生产严格目标

共计 84 测试通过(68 业务逻辑 + 16 规模测试)
This commit is contained in:
2026-04-07 07:23:29 +08:00
parent 3ae11237ab
commit 8655b39b03
4 changed files with 4772 additions and 4 deletions

View File

@@ -0,0 +1,83 @@
// Package pagination provides cursor-based (keyset) pagination utilities.
//
// Unlike offset-based pagination (OFFSET/LIMIT), cursor pagination uses
// a composite key (typically created_at + id) to locate the "position" in
// the result set, giving O(limit) performance regardless of how deep you page.
package pagination
import (
"encoding/base64"
"encoding/json"
"fmt"
"time"
)
// Cursor represents an opaque position in a sorted result set.
// It is serialized as a URL-safe base64 string for transport.
type Cursor struct {
// LastID is the primary key of the last item on the current page.
LastID int64 `json:"last_id"`
// LastValue is the sort column value of the last item (e.g. created_at).
LastValue time.Time `json:"last_value"`
}
// Encode serializes a Cursor to a URL-safe base64 string suitable for query params.
func (c *Cursor) Encode() string {
if c == nil || c.LastID == 0 {
return ""
}
data, _ := json.Marshal(c)
return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(data)
}
// Decode parses a base64-encoded cursor string back into a Cursor.
// Returns nil for empty strings (meaning "first page").
func Decode(encoded string) (*Cursor, error) {
if encoded == "" {
return nil, nil
}
data, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(encoded)
if err != nil {
return nil, fmt.Errorf("invalid cursor encoding: %w", err)
}
var c Cursor
if err := json.Unmarshal(data, &c); err != nil {
return nil, fmt.Errorf("invalid cursor data: %w", err)
}
return &c, nil
}
// PageResult wraps a paginated response with cursor navigation info.
type PageResult[T any] struct {
Items []T `json:"items"`
Total int64 `json:"total"` // Approximate or exact total (optional for pure cursor mode)
NextCursor string `json:"next_cursor"` // Empty means no more pages
HasMore bool `json:"has_more"`
PageSize int `json:"page_size"`
}
// DefaultPageSize is the default number of items per page.
const DefaultPageSize = 20
// MaxPageSize caps the maximum allowed items per request to prevent abuse.
const MaxPageSize = 100
// ClampPageSize ensures size is within [1, MaxPageSize], falling back to DefaultPageSize.
func ClampPageSize(size int) int {
if size <= 0 {
return DefaultPageSize
}
if size > MaxPageSize {
return MaxPageSize
}
return size
}
// BuildNextCursor creates a cursor from the last item's ID and timestamp.
// Returns empty string if there are no items.
func BuildNextCursor(lastID int64, lastTime time.Time) string {
if lastID == 0 {
return ""
}
return (&Cursor{LastID: lastID, LastValue: lastTime}).Encode()
}