Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+373
View File
@@ -0,0 +1,373 @@
package shortrecommendser
import (
"context"
"crypto/sha256"
"errors"
"fmt"
"strings"
"time"
"91porn-server/app/appg"
recommendqueue "91porn-server/common/shortrecommend"
"91porn-server/models/v/moduleconfmod"
"91porn-server/models/v/vidmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
const (
defaultMaxBatches = 5
defaultScanMultiplier = 5
maxAllowedBatches = 20
maxAllowedMultiplier = 20
maxRequestIDLength = 128
reservationAbortTTL = 2 * time.Second
reservationBusyRetries = 3
reservationBusyBackoff = 10 * time.Millisecond
)
// FetchResult 是一次环形队列拉取的结果和可观测指标。
type FetchResult struct {
Videos []*vidmod.VideoModel
QueueVersion string
QueueLength int
Scanned int
Filtered int
Batches int
BudgetExceeded bool
}
type fetchOptions struct {
maxBatches int
scanMultiplier int
}
type reserveFunc func(
context.Context,
uint64,
int,
string,
) (recommendqueue.Reservation, error)
type fetchDependencies struct {
reserve reserveFunc
commit func(
context.Context,
uint64,
recommendqueue.Reservation,
int,
) error
abort func(
context.Context,
uint64,
recommendqueue.Reservation,
) error
excludedModuleIDs func(time.Time, bool) ([]string, error)
findVideos func(context.Context, []primitive.ObjectID) ([]*vidmod.VideoModel, error)
now func() time.Time
}
// Fetch 从用户当前偏移预留短视频,按当前Mongo状态过滤,并限制补位扫描成本。
// 只有全部Mongo批次成功后才一次性推进实际扫描前缀;失败会释放预留且不跳内容。
func Fetch(
ctx context.Context,
uid uint64,
size int,
requestID string,
) (FetchResult, error) {
return FetchScoped(ctx, uid, size, requestID, "short-recommend")
}
// FetchScoped 将入口和请求大小纳入幂等作用域,防止同一客户端requestID
// 被不同短视频接口或不同请求参数误复用。
func FetchScoped(
ctx context.Context,
uid uint64,
size int,
requestID, scope string,
) (FetchResult, error) {
if appg.Redis == nil {
return FetchResult{}, fmt.Errorf("short recommend Redis is nil")
}
var err error
requestID, err = scopedRequestID(scope, size, requestID)
if err != nil {
return FetchResult{}, err
}
opts := configuredFetchOptions()
deps := fetchDependencies{
reserve: func(
ctx context.Context,
uid uint64,
size int,
requestID string,
) (recommendqueue.Reservation, error) {
return recommendqueue.ReserveCurrentContext(
ctx, appg.Redis, uid, size, requestID,
)
},
commit: func(
ctx context.Context,
uid uint64,
reservation recommendqueue.Reservation,
consumed int,
) error {
return recommendqueue.CommitReservationContext(
ctx, appg.Redis, uid, reservation, consumed,
)
},
abort: func(
ctx context.Context,
uid uint64,
reservation recommendqueue.Reservation,
) error {
return recommendqueue.AbortReservationContext(
ctx, appg.Redis, uid, reservation,
)
},
excludedModuleIDs: moduleconfmod.ExcludedVideoModuleIDs,
findVideos: vidmod.GetRecommendVideosByIDsContext,
now: time.Now,
}
return fetch(ctx, uid, size, requestID, opts, deps)
}
func scopedRequestID(scope string, size int, requestID string) (string, error) {
requestID = strings.TrimSpace(requestID)
if requestID == "" {
return "", nil
}
if len(requestID) > maxRequestIDLength {
return "", fmt.Errorf("short recommend request ID exceeds %d bytes", maxRequestIDLength)
}
sum := sha256.Sum256([]byte(fmt.Sprintf("%s:%d:%s", scope, size, requestID)))
return fmt.Sprintf("%x", sum[:16]), nil
}
func configuredFetchOptions() fetchOptions {
opts := fetchOptions{
maxBatches: defaultMaxBatches,
scanMultiplier: defaultScanMultiplier,
}
if appg.Conf == nil {
return opts
}
if configured := appg.Conf.ShortRecommend.MaxBatches; configured > 0 {
opts.maxBatches = clamp(configured, 1, maxAllowedBatches)
}
if configured := appg.Conf.ShortRecommend.ScanMultiplier; configured > 0 {
opts.scanMultiplier = clamp(configured, 1, maxAllowedMultiplier)
}
return opts
}
func fetch(
ctx context.Context,
uid uint64,
size int,
requestID string,
opts fetchOptions,
deps fetchDependencies,
) (result FetchResult, err error) {
if ctx == nil {
return result, fmt.Errorf("short recommend context must not be nil")
}
if err = ctx.Err(); err != nil {
return result, err
}
if uid == 0 {
return result, fmt.Errorf("anonymous user has no independent queue offset")
}
if size <= 0 {
return result, nil
}
requestID = strings.TrimSpace(requestID)
if len(requestID) > maxRequestIDLength {
return result, fmt.Errorf("short recommend request ID exceeds %d bytes", maxRequestIDLength)
}
opts.maxBatches = clamp(opts.maxBatches, 1, maxAllowedBatches)
opts.scanMultiplier = clamp(opts.scanMultiplier, 1, maxAllowedMultiplier)
scanBudget := size * opts.scanMultiplier
if scanBudget < size {
scanBudget = size
}
if batchBudget := size * opts.maxBatches; scanBudget > batchBudget {
// 预留不会超过本次最多能查询的范围,避免把永远不会扫描的ID
// 从Redis传到App进程。
scanBudget = batchBudget
}
excluded, err := deps.excludedModuleIDs(deps.now(), true)
if err != nil {
return result, err
}
excludedSet := make(map[string]struct{}, len(excluded))
for _, moduleID := range excluded {
excludedSet[moduleID] = struct{}{}
}
reservation, reserveErr := reserveWithBusyRetry(
ctx, deps.reserve, uid, scanBudget, requestID,
)
result.QueueVersion = reservation.Version
result.QueueLength = reservation.Length
if reserveErr != nil {
return result, reserveErr
}
if reservation.Version == "" || reservation.Length <= 0 ||
reservation.Reserved <= 0 || len(reservation.IDs) == 0 {
return result, nil
}
committed := false
defer func() {
if committed {
return
}
abortCtx, cancel := context.WithTimeout(context.Background(), reservationAbortTTL)
defer cancel()
_ = deps.abort(abortCtx, uid, reservation)
}()
result.Videos = make([]*vidmod.VideoModel, 0, size)
seen := make(map[primitive.ObjectID]struct{}, size)
cursor := 0
for cursor < len(reservation.IDs) &&
(reservation.AlreadyCommitted ||
(result.Batches < opts.maxBatches && len(result.Videos) < size)) {
batchSize := size
if !reservation.AlreadyCommitted {
batchSize = size - len(result.Videos)
} else if batchSize > 0 {
// 已提交请求的receipt记录的是原实际消费前缀。重试必须扫描并
// 确认完整前缀,再以原consumed幂等提交,不能因当前状态变化
// 缩短已确认的消费范围。固定按请求大小分批,避免当前只差一条
// 有效视频时退化为大量单ID Mongo查询。
}
if remaining := len(reservation.IDs) - cursor; batchSize > remaining {
batchSize = remaining
}
if batchSize <= 0 {
break
}
end := cursor + batchSize
batchIDs := reservation.IDs[cursor:end]
objectIDs := parseUniqueObjectIDs(batchIDs)
videos, findErr := deps.findVideos(ctx, objectIDs)
if findErr != nil {
result.Videos = nil
return result, findErr
}
result.Batches++
result.Scanned += len(batchIDs)
cursor = end
byID := make(map[primitive.ObjectID]*vidmod.VideoModel, len(videos))
for _, video := range videos {
if video == nil {
continue
}
if _, blocked := excludedSet[video.MID]; blocked {
continue
}
byID[video.ID] = video
}
before := len(result.Videos)
for _, rawID := range batchIDs {
if len(result.Videos) >= size {
break
}
id, parseErr := primitive.ObjectIDFromHex(rawID)
if parseErr != nil {
continue
}
if _, exists := seen[id]; exists {
continue
}
video := byID[id]
if video == nil {
continue
}
seen[id] = struct{}{}
result.Videos = append(result.Videos, video)
if len(result.Videos) == size {
break
}
}
result.Filtered += len(batchIDs) - (len(result.Videos) - before)
}
if result.Scanned <= 0 {
result.Videos = nil
return result, fmt.Errorf("short recommend reservation was not scanned")
}
commitErr := deps.commit(ctx, uid, reservation, result.Scanned)
if commitErr != nil && ctx.Err() == nil {
// 同一receipt重试可确认“脚本已执行但响应丢失”的不确定提交,
// 不会再次推进offset。
commitErr = deps.commit(ctx, uid, reservation, result.Scanned)
}
if commitErr != nil {
result.Videos = nil
return result, commitErr
}
committed = true
result.BudgetExceeded = len(result.Videos) < size &&
result.QueueLength > 0 &&
result.Scanned < result.QueueLength &&
(result.Scanned >= scanBudget || result.Batches >= opts.maxBatches)
return result, nil
}
func reserveWithBusyRetry(
ctx context.Context,
reserve reserveFunc,
uid uint64,
size int,
requestID string,
) (recommendqueue.Reservation, error) {
var reservation recommendqueue.Reservation
var err error
for attempt := 0; attempt <= reservationBusyRetries; attempt++ {
reservation, err = reserve(ctx, uid, size, requestID)
if !errors.Is(err, recommendqueue.ErrReservationBusy) ||
attempt == reservationBusyRetries {
return reservation, err
}
delay := reservationBusyBackoff << attempt
timer := time.NewTimer(delay)
select {
case <-ctx.Done():
timer.Stop()
return reservation, ctx.Err()
case <-timer.C:
}
}
return reservation, err
}
func parseUniqueObjectIDs(ids []string) []primitive.ObjectID {
out := make([]primitive.ObjectID, 0, len(ids))
seen := make(map[primitive.ObjectID]struct{}, len(ids))
for _, rawID := range ids {
id, err := primitive.ObjectIDFromHex(rawID)
if err != nil {
continue
}
if _, exists := seen[id]; exists {
continue
}
seen[id] = struct{}{}
out = append(out, id)
}
return out
}
func clamp(value, low, high int) int {
if value < low {
return low
}
if value > high {
return high
}
return value
}
+523
View File
@@ -0,0 +1,523 @@
package shortrecommendser
import (
"context"
"errors"
"testing"
"time"
recommendqueue "91porn-server/common/shortrecommend"
"91porn-server/models/v/vidmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func TestFetchReservesOncePreservesOrderAndCommitsConsumedPrefix(t *testing.T) {
validA := primitive.NewObjectID()
downShelf := primitive.NewObjectID()
excluded := primitive.NewObjectID()
validB := primitive.NewObjectID()
ids := []string{validA.Hex(), downShelf.Hex(), excluded.Hex(), validB.Hex()}
deps := testFetchDependencies(
testReservation(ids, len(ids)),
func(_ context.Context, batch []primitive.ObjectID) ([]*vidmod.VideoModel, error) {
out := make([]*vidmod.VideoModel, 0, len(batch))
for _, id := range batch {
switch id {
case validA, validB:
out = append(out, &vidmod.VideoModel{ID: id})
case excluded:
out = append(out, &vidmod.VideoModel{ID: id, MID: "blocked"})
}
}
return out, nil
},
)
deps.excludedModuleIDs = func(time.Time, bool) ([]string, error) {
return []string{"blocked"}, nil
}
reserveCalls, committed, aborted := 0, 0, 0
originalReserve := deps.reserve
deps.reserve = func(
ctx context.Context, uid uint64, size int, requestID string,
) (recommendqueue.Reservation, error) {
reserveCalls++
if size != 10 {
t.Fatalf("reserve size = %d, want scan budget 10", size)
}
return originalReserve(ctx, uid, size, requestID)
}
deps.commit = func(
_ context.Context,
_ uint64,
_ recommendqueue.Reservation,
consumed int,
) error {
committed = consumed
return nil
}
deps.abort = func(
context.Context, uint64, recommendqueue.Reservation,
) error {
aborted++
return nil
}
result, err := fetch(context.Background(), 7, 2, "request-1", fetchOptions{
maxBatches: 5, scanMultiplier: 5,
}, deps)
if err != nil {
t.Fatalf("fetch() error = %v", err)
}
if len(result.Videos) != 2 ||
result.Videos[0].ID != validA ||
result.Videos[1].ID != validB {
t.Fatalf("videos = %#v, want queue ordered valid videos", result.Videos)
}
if reserveCalls != 1 || committed != 4 || aborted != 0 {
t.Fatalf("reserve=%d committed=%d aborted=%d", reserveCalls, committed, aborted)
}
if result.Scanned != 4 || result.Filtered != 2 || result.Batches != 3 {
t.Fatalf("metrics = %+v", result)
}
if result.QueueVersion != "20260731-r1" || result.QueueLength != len(ids) {
t.Fatalf("queue metadata = %+v", result)
}
if result.BudgetExceeded {
t.Fatal("BudgetExceeded = true, want false after a full queue scan")
}
}
func TestFetchCommitsOnlyBoundedScannedPrefix(t *testing.T) {
ids := make([]string, 30)
for i := range ids {
ids[i] = primitive.NewObjectID().Hex()
}
reservation := testReservation(ids, 100)
deps := testFetchDependencies(
reservation,
func(context.Context, []primitive.ObjectID) ([]*vidmod.VideoModel, error) {
return nil, nil
},
)
committed := 0
deps.commit = func(
_ context.Context,
_ uint64,
_ recommendqueue.Reservation,
consumed int,
) error {
committed = consumed
return nil
}
result, err := fetch(context.Background(), 8, 3, "request-2", fetchOptions{
maxBatches: 2, scanMultiplier: 10,
}, deps)
if err != nil {
t.Fatalf("fetch() error = %v", err)
}
if result.Batches != 2 || result.Scanned != 6 || committed != 6 {
t.Fatalf("committed=%d result=%+v", committed, result)
}
if !result.BudgetExceeded {
t.Fatal("BudgetExceeded = false, want true")
}
}
func TestFetchMongoFailureAbortsWithoutCommitOrPartialResponse(t *testing.T) {
valid := primitive.NewObjectID()
filtered := primitive.NewObjectID()
failing := primitive.NewObjectID()
wantErr := errors.New("mongo unavailable")
findCalls := 0
deps := testFetchDependencies(
testReservation(
[]string{valid.Hex(), filtered.Hex(), failing.Hex()},
10,
),
func(_ context.Context, ids []primitive.ObjectID) ([]*vidmod.VideoModel, error) {
findCalls++
if findCalls == 1 {
return []*vidmod.VideoModel{{ID: valid}}, nil
}
return nil, wantErr
},
)
commitCalls, abortCalls := 0, 0
deps.commit = func(
context.Context, uint64, recommendqueue.Reservation, int,
) error {
commitCalls++
return nil
}
deps.abort = func(
context.Context, uint64, recommendqueue.Reservation,
) error {
abortCalls++
return nil
}
result, err := fetch(context.Background(), 9, 2, "request-3", fetchOptions{
maxBatches: 5, scanMultiplier: 5,
}, deps)
if !errors.Is(err, wantErr) {
t.Fatalf("error = %v, want %v", err, wantErr)
}
if result.Videos != nil || commitCalls != 0 || abortCalls != 1 {
t.Fatalf("result=%+v commitCalls=%d abortCalls=%d", result, commitCalls, abortCalls)
}
if result.Scanned != 2 || result.Batches != 1 {
t.Fatalf("successful scan metrics = %+v", result)
}
}
func TestFetchRetriesUncertainCommitWithSameReservation(t *testing.T) {
id := primitive.NewObjectID()
wantErr := errors.New("connection reset after write")
deps := testFetchDependencies(
testReservation([]string{id.Hex()}, 10),
func(context.Context, []primitive.ObjectID) ([]*vidmod.VideoModel, error) {
return []*vidmod.VideoModel{{ID: id}}, nil
},
)
commitCalls := 0
deps.commit = func(
context.Context, uint64, recommendqueue.Reservation, int,
) error {
commitCalls++
if commitCalls == 1 {
return wantErr
}
return nil
}
result, err := fetch(context.Background(), 10, 1, "request-4", fetchOptions{
maxBatches: 5, scanMultiplier: 5,
}, deps)
if err != nil {
t.Fatalf("fetch() error = %v", err)
}
if commitCalls != 2 || len(result.Videos) != 1 {
t.Fatalf("commitCalls=%d result=%+v", commitCalls, result)
}
}
func TestFetchCommitFailureAbortsAndSuppressesVideos(t *testing.T) {
id := primitive.NewObjectID()
wantErr := errors.New("redis unavailable")
deps := testFetchDependencies(
testReservation([]string{id.Hex()}, 10),
func(context.Context, []primitive.ObjectID) ([]*vidmod.VideoModel, error) {
return []*vidmod.VideoModel{{ID: id}}, nil
},
)
commitCalls, abortCalls := 0, 0
deps.commit = func(
context.Context, uint64, recommendqueue.Reservation, int,
) error {
commitCalls++
return wantErr
}
deps.abort = func(
context.Context, uint64, recommendqueue.Reservation,
) error {
abortCalls++
return nil
}
result, err := fetch(context.Background(), 11, 1, "request-5", fetchOptions{
maxBatches: 5, scanMultiplier: 5,
}, deps)
if !errors.Is(err, wantErr) {
t.Fatalf("error = %v, want %v", err, wantErr)
}
if commitCalls != 2 || abortCalls != 1 || result.Videos != nil {
t.Fatalf("commit=%d abort=%d result=%+v", commitCalls, abortCalls, result)
}
}
func TestFetchReservationErrorCarriesQueueMetadata(t *testing.T) {
deps := testFetchDependencies(recommendqueue.Reservation{}, nil)
deps.reserve = func(
context.Context, uint64, int, string,
) (recommendqueue.Reservation, error) {
return recommendqueue.Reservation{
Version: "20260731-r2",
Length: 100,
}, recommendqueue.ErrReservationBusy
}
result, err := fetch(context.Background(), 12, 1, "", fetchOptions{}, deps)
if !errors.Is(err, recommendqueue.ErrReservationBusy) {
t.Fatalf("error = %v, want ErrReservationBusy", err)
}
if result.QueueVersion != "20260731-r2" || result.QueueLength != 100 {
t.Fatalf("result = %+v", result)
}
}
func TestFetchRetriesBusyReservationWithinRequest(t *testing.T) {
id := primitive.NewObjectID()
reservation := testReservation([]string{id.Hex()}, 10)
deps := testFetchDependencies(
reservation,
func(context.Context, []primitive.ObjectID) ([]*vidmod.VideoModel, error) {
return []*vidmod.VideoModel{{ID: id}}, nil
},
)
reserveCalls := 0
deps.reserve = func(
context.Context, uint64, int, string,
) (recommendqueue.Reservation, error) {
reserveCalls++
if reserveCalls < 3 {
return recommendqueue.Reservation{
Version: reservation.Version,
Length: reservation.Length,
}, recommendqueue.ErrReservationBusy
}
return reservation, nil
}
result, err := fetch(context.Background(), 12, 1, "", fetchOptions{}, deps)
if err != nil {
t.Fatalf("fetch() error = %v", err)
}
if reserveCalls != 3 || len(result.Videos) != 1 {
t.Fatalf("reserveCalls=%d result=%+v", reserveCalls, result)
}
}
func TestReserveBusyRetryHonorsCancellation(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
calls := 0
_, err := reserveWithBusyRetry(
ctx,
func(
context.Context, uint64, int, string,
) (recommendqueue.Reservation, error) {
calls++
cancel()
return recommendqueue.Reservation{}, recommendqueue.ErrReservationBusy
},
1,
20,
"",
)
if !errors.Is(err, context.Canceled) || calls != 1 {
t.Fatalf("error=%v calls=%d", err, calls)
}
}
func TestFetchRejectsCanceledContextBeforeDependencies(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
called := false
deps := testFetchDependencies(recommendqueue.Reservation{}, nil)
deps.reserve = func(
context.Context, uint64, int, string,
) (recommendqueue.Reservation, error) {
called = true
return recommendqueue.Reservation{}, nil
}
_, err := fetch(ctx, 13, 1, "request-6", fetchOptions{}, deps)
if !errors.Is(err, context.Canceled) {
t.Fatalf("error = %v, want context.Canceled", err)
}
if called {
t.Fatal("dependency called after context cancellation")
}
}
func TestFetchWithoutClientRequestIDUsesReservationWithoutBusinessReceiptScope(t *testing.T) {
id := primitive.NewObjectID()
deps := testFetchDependencies(
testReservation([]string{id.Hex()}, 1),
func(context.Context, []primitive.ObjectID) ([]*vidmod.VideoModel, error) {
return []*vidmod.VideoModel{{ID: id}}, nil
},
)
gotRequestID := "not-called"
originalReserve := deps.reserve
deps.reserve = func(
ctx context.Context, uid uint64, size int, requestID string,
) (recommendqueue.Reservation, error) {
gotRequestID = requestID
return originalReserve(ctx, uid, size, requestID)
}
result, err := fetch(context.Background(), 14, 1, "", fetchOptions{
maxBatches: 5, scanMultiplier: 5,
}, deps)
if err != nil {
t.Fatalf("fetch() error = %v", err)
}
if gotRequestID != "" || len(result.Videos) != 1 {
t.Fatalf("requestID=%q result=%+v", gotRequestID, result)
}
}
func TestFetchCommittedReceiptReplaysEntireConsumedPrefix(t *testing.T) {
ids := []string{
primitive.NewObjectID().Hex(),
primitive.NewObjectID().Hex(),
primitive.NewObjectID().Hex(),
}
reservation := testReservation(ids, 10)
reservation.AlreadyCommitted = true
deps := testFetchDependencies(
reservation,
func(_ context.Context, batch []primitive.ObjectID) ([]*vidmod.VideoModel, error) {
out := make([]*vidmod.VideoModel, 0, len(batch))
for _, id := range batch {
out = append(out, &vidmod.VideoModel{ID: id})
}
return out, nil
},
)
committed := 0
deps.commit = func(
_ context.Context,
_ uint64,
_ recommendqueue.Reservation,
consumed int,
) error {
committed = consumed
return nil
}
result, err := fetch(context.Background(), 15, 1, "request-retry", fetchOptions{
maxBatches: 1, scanMultiplier: 1,
}, deps)
if err != nil {
t.Fatal(err)
}
if committed != len(ids) || result.Scanned != len(ids) ||
len(result.Videos) != 1 {
t.Fatalf("committed=%d result=%+v", committed, result)
}
}
func TestFetchCommittedReceiptUsesBoundedMongoBatches(t *testing.T) {
const (
requestSize = 20
consumed = 100
)
ids := make([]string, consumed)
for i := range ids {
ids[i] = primitive.NewObjectID().Hex()
}
reservation := testReservation(ids, consumed)
reservation.AlreadyCommitted = true
batchSizes := make([]int, 0, consumed/requestSize)
deps := testFetchDependencies(
reservation,
func(_ context.Context, batch []primitive.ObjectID) ([]*vidmod.VideoModel, error) {
batchSizes = append(batchSizes, len(batch))
if len(batchSizes) > 1 {
return nil, nil
}
videos := make([]*vidmod.VideoModel, 0, requestSize-1)
for _, id := range batch[:requestSize-1] {
videos = append(videos, &vidmod.VideoModel{ID: id})
}
return videos, nil
},
)
committed := 0
deps.commit = func(
_ context.Context,
_ uint64,
_ recommendqueue.Reservation,
value int,
) error {
committed = value
return nil
}
result, err := fetch(context.Background(), 16, requestSize, "request-retry", fetchOptions{
maxBatches: 1, scanMultiplier: 1,
}, deps)
if err != nil {
t.Fatal(err)
}
if len(batchSizes) != consumed/requestSize {
t.Fatalf("Mongo calls=%d batchSizes=%v", len(batchSizes), batchSizes)
}
for _, batchSize := range batchSizes {
if batchSize != requestSize {
t.Fatalf("batchSizes=%v", batchSizes)
}
}
if committed != consumed || result.Scanned != consumed ||
len(result.Videos) != requestSize-1 {
t.Fatalf("committed=%d result=%+v", committed, result)
}
}
func TestScopedRequestIDSeparatesEntryAndRequestSize(t *testing.T) {
first, err := scopedRequestID("recommend-list", 20, "request")
if err != nil {
t.Fatal(err)
}
same, _ := scopedRequestID("recommend-list", 20, " request ")
otherEntry, _ := scopedRequestID("module-short-all", 20, "request")
otherSize, _ := scopedRequestID("recommend-list", 10, "request")
if first == "" || first != same {
t.Fatalf("scoped IDs first=%q same=%q", first, same)
}
if first == otherEntry || first == otherSize || otherEntry == otherSize {
t.Fatalf("scope collision: %q %q %q", first, otherEntry, otherSize)
}
blank, err := scopedRequestID("recommend-list", 20, " ")
if err != nil || blank != "" {
t.Fatalf("blank request ID = %q, %v", blank, err)
}
}
func testReservation(ids []string, length int) recommendqueue.Reservation {
return recommendqueue.Reservation{
Version: "20260731-r1",
Length: length,
Offset: 0,
Reserved: len(ids),
IDs: append([]string(nil), ids...),
LeaseToken: "lease",
ReceiptID: "receipt",
}
}
func testFetchDependencies(
reservation recommendqueue.Reservation,
find func(context.Context, []primitive.ObjectID) ([]*vidmod.VideoModel, error),
) fetchDependencies {
if find == nil {
find = func(context.Context, []primitive.ObjectID) ([]*vidmod.VideoModel, error) {
return nil, nil
}
}
return fetchDependencies{
reserve: func(
context.Context, uint64, int, string,
) (recommendqueue.Reservation, error) {
return reservation, nil
},
commit: func(
context.Context, uint64, recommendqueue.Reservation, int,
) error {
return nil
},
abort: func(
context.Context, uint64, recommendqueue.Reservation,
) error {
return nil
},
excludedModuleIDs: func(time.Time, bool) ([]string, error) {
return nil, nil
},
findVideos: find,
now: time.Now,
}
}