Files
geo/server/internal/tenant/app/kol_profile_cache_test.go
T
root 8761e47f78 feat(kol): cache marketplace and generation reads with singleflight
Add cache-aside + singleflight to KOL marketplace list/detail, my-subscription
prompts, and generation schema lookups. Invalidate the shared public-facing
caches when subscriptions are added/revoked (ops) or KOL profile is updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 11:33:17 +08:00

87 lines
2.6 KiB
Go

package app
import (
"context"
"errors"
"testing"
"time"
"github.com/geo-platform/tenant-api/internal/shared/auth"
sharedcache "github.com/geo-platform/tenant-api/internal/shared/cache"
"github.com/geo-platform/tenant-api/internal/tenant/repository"
)
type stubKolProfileRepository struct {
profile repository.KolProfile
}
func (r *stubKolProfileRepository) GetByUser(ctx context.Context, tenantID, userID int64) (*repository.KolProfile, error) {
if r.profile.TenantID != tenantID || r.profile.UserID != userID {
return nil, nil
}
profile := r.profile
return &profile, nil
}
func (r *stubKolProfileRepository) GetByID(ctx context.Context, id int64) (*repository.KolProfile, error) {
if r.profile.ID != id {
return nil, nil
}
profile := r.profile
return &profile, nil
}
func (r *stubKolProfileRepository) Update(ctx context.Context, tenantID int64, input repository.UpdateKolProfileInput) error {
if r.profile.TenantID != tenantID || r.profile.ID != input.ID {
return errors.New("unexpected profile update target")
}
r.profile.DisplayName = input.DisplayName
r.profile.Bio = input.Bio
r.profile.AvatarURL = input.AvatarURL
r.profile.UpdatedAt = time.Now()
return nil
}
func TestUpdateKolProfileInvalidatesPublicCaches(t *testing.T) {
t.Parallel()
ctx := context.Background()
cache := sharedcache.New("memory", nil, sharedcache.WithNamespace("test"), sharedcache.WithMetrics(false), sharedcache.WithJitterRatio(0))
tenantID := int64(42)
keys := []string{
workspaceKolCardsCacheKey(tenantID),
kolMarketplacePackageListCacheKey(tenantID, MarketFilter{Limit: 20}),
kolMarketplacePackageDetailCacheKey(tenantID, 7),
kolSubscriptionPromptListCacheKey(tenantID),
kolSubscriptionPromptSchemaCacheKey(tenantID, 9),
}
for _, key := range keys {
if err := cache.Set(ctx, key, []byte(`{"value":"stale"}`), time.Minute); err != nil {
t.Fatalf("seed cache %s: %v", key, err)
}
}
repo := &stubKolProfileRepository{profile: repository.KolProfile{
ID: 3,
TenantID: tenantID,
UserID: 5,
DisplayName: "old name",
MarketEnabled: true,
Status: "active",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}}
svc := NewKolProfileService(repo).WithCache(cache)
if _, err := svc.UpdateProfile(ctx, auth.Actor{TenantID: tenantID, UserID: 5}, UpdateKolProfileServiceInput{DisplayName: "new name"}); err != nil {
t.Fatalf("UpdateProfile() error = %v", err)
}
for _, key := range keys {
if _, err := cache.Get(ctx, key); !errors.Is(err, sharedcache.ErrNotFound) {
t.Fatalf("cache key %s should be invalidated, got err %v", key, err)
}
}
}