naco-api/internal/server/storefake_test.go
Nacorid d1b7805162
test(server): sort fake store analytics posts ascending
GetAnalyticsPostsInRange now returns posts oldest-first to match the real store
implementation.
2026-09-13 22:24:45 +00:00

214 lines
6.4 KiB
Go

package server
import (
"context"
"database/sql"
"encoding/json"
"sort"
"time"
"github.com/nacorid/naco-api/internal/store"
)
// fakeStore is an in-memory implementation of the Store interface used by handler
// tests so db-backed tool paths (style profiles, analytics, similar users) can be
// exercised without a live Postgres+pgvector.
type fakeStore struct {
analyticsCache map[cacheKey]cacheEntry
analyticsPosts map[string]store.AnalyticsPost
coverageRanges []store.AnalyticsCoverageRange
styleProfiles map[string]store.UserStyleProfile
stylePosts map[string]store.UserStylePost
similarRows []store.FindSimilarUsersRow
findSimilarErr error
styleProfileErr error
stylePostsErr error
cacheErr bool
postsRangeErr bool
coverageErr bool
}
type cacheKey struct {
user, platform, tool, hash string
}
type cacheEntry struct {
value json.RawMessage
exp time.Time
}
func newFakeStore() *fakeStore {
return &fakeStore{
analyticsCache: map[cacheKey]cacheEntry{},
analyticsPosts: map[string]store.AnalyticsPost{},
styleProfiles: map[string]store.UserStyleProfile{},
stylePosts: map[string]store.UserStylePost{},
}
}
func postKey(user, platform, id string) string {
return user + "|" + platform + "|" + id
}
func profileKey(user, platform string) string {
return user + "|" + platform
}
func (f *fakeStore) GetAnalyticsCache(ctx context.Context, arg store.GetAnalyticsCacheParams) (json.RawMessage, error) {
if f.cacheErr {
return nil, sql.ErrNoRows
}
key := cacheKey{arg.UserID, arg.Platform, arg.ToolName, arg.TimeRangeHash}
entry, ok := f.analyticsCache[key]
if !ok || time.Now().After(entry.exp) {
return nil, sql.ErrNoRows
}
return entry.value, nil
}
func (f *fakeStore) UpsertAnalyticsCache(ctx context.Context, arg store.UpsertAnalyticsCacheParams) error {
key := cacheKey{arg.UserID, arg.Platform, arg.ToolName, arg.TimeRangeHash}
f.analyticsCache[key] = cacheEntry{value: arg.Result, exp: arg.ExpiresAt}
return nil
}
func (f *fakeStore) GetAnalyticsPostsInRange(ctx context.Context, arg store.GetAnalyticsPostsInRangeParams) ([]store.GetAnalyticsPostsInRangeRow, error) {
if f.postsRangeErr {
return nil, sql.ErrConnDone
}
var rows []store.GetAnalyticsPostsInRangeRow
for _, p := range f.analyticsPosts {
if p.UserID != arg.UserID || p.Platform != arg.Platform {
continue
}
if p.PostTimestamp.Before(arg.PostTimestamp) || p.PostTimestamp.After(arg.PostTimestamp_2) {
continue
}
rows = append(rows, store.GetAnalyticsPostsInRangeRow{
PostID: p.PostID,
PostText: p.PostText,
PostTimestamp: p.PostTimestamp,
Likes: p.Likes,
Reposts: p.Reposts,
Replies: p.Replies,
Quotes: p.Quotes,
})
}
sort.Slice(rows, func(i, j int) bool { return rows[i].PostTimestamp.Before(rows[j].PostTimestamp) })
if int32(len(rows)) > arg.Limit {
rows = rows[:arg.Limit]
}
return rows, nil
}
func (f *fakeStore) UpsertAnalyticsPosts(ctx context.Context, arg store.UpsertAnalyticsPostsParams) error {
f.analyticsPosts[postKey(arg.UserID, arg.Platform, arg.PostID)] = store.AnalyticsPost{
UserID: arg.UserID,
Platform: arg.Platform,
PostID: arg.PostID,
PostText: arg.PostText,
PostTimestamp: arg.PostTimestamp,
Likes: arg.Likes,
Reposts: arg.Reposts,
Replies: arg.Replies,
Quotes: arg.Quotes,
}
return nil
}
func (f *fakeStore) GetAnalyticsCoverageRanges(ctx context.Context, arg store.GetAnalyticsCoverageRangesParams) ([]store.GetAnalyticsCoverageRangesRow, error) {
if f.coverageErr {
return nil, sql.ErrConnDone
}
var rows []store.GetAnalyticsCoverageRangesRow
for _, r := range f.coverageRanges {
if r.UserID != arg.UserID || r.Platform != arg.Platform {
continue
}
rows = append(rows, store.GetAnalyticsCoverageRangesRow{CoveredStart: r.CoveredStart, CoveredEnd: r.CoveredEnd})
}
return rows, nil
}
func (f *fakeStore) InsertAnalyticsCoverageRange(ctx context.Context, arg store.InsertAnalyticsCoverageRangeParams) error {
f.coverageRanges = append(f.coverageRanges, store.AnalyticsCoverageRange{
UserID: arg.UserID,
Platform: arg.Platform,
CoveredStart: arg.CoveredStart,
CoveredEnd: arg.CoveredEnd,
})
return nil
}
func (f *fakeStore) GetStyleProfile(ctx context.Context, arg store.GetStyleProfileParams) (store.UserStyleProfile, error) {
if f.styleProfileErr != nil {
return store.UserStyleProfile{}, f.styleProfileErr
}
p, ok := f.styleProfiles[profileKey(arg.UserID, arg.Platform)]
if !ok {
return store.UserStyleProfile{}, sql.ErrNoRows
}
return p, nil
}
func (f *fakeStore) UpsertStyleProfile(ctx context.Context, arg store.UpsertStyleProfileParams) error {
f.styleProfiles[profileKey(arg.UserID, arg.Platform)] = store.UserStyleProfile{
UserID: arg.UserID,
Platform: arg.Platform,
Embedding: arg.Embedding,
TopKeywords: arg.TopKeywords,
StyleSummary: arg.StyleSummary,
PostCount: arg.PostCount,
ModifiedAt: sql.NullTime{Time: time.Now(), Valid: true},
}
return nil
}
func (f *fakeStore) DeleteStylePosts(ctx context.Context, arg store.DeleteStylePostsParams) error {
for _, k := range f.stylePosts {
if k.UserID == arg.UserID && k.Platform == arg.Platform {
delete(f.stylePosts, postKey(k.UserID, k.Platform, k.PostID))
}
}
return nil
}
func (f *fakeStore) GetStylePosts(ctx context.Context, arg store.GetStylePostsParams) ([]store.GetStylePostsRow, error) {
if f.stylePostsErr != nil {
return nil, f.stylePostsErr
}
var rows []store.GetStylePostsRow
for _, p := range f.stylePosts {
if p.UserID != arg.UserID || p.Platform != arg.Platform {
continue
}
rows = append(rows, store.GetStylePostsRow{PostID: p.PostID, PostText: p.PostText})
}
sort.Slice(rows, func(i, j int) bool { return rows[i].PostID > rows[j].PostID })
if int32(len(rows)) > arg.Limit {
rows = rows[:arg.Limit]
}
return rows, nil
}
func (f *fakeStore) UpsertStylePosts(ctx context.Context, arg store.UpsertStylePostsParams) error {
f.stylePosts[postKey(arg.UserID, arg.Platform, arg.PostID)] = store.UserStylePost{
UserID: arg.UserID,
Platform: arg.Platform,
PostID: arg.PostID,
PostText: arg.PostText,
CreatedAt: time.Now(),
}
return nil
}
func (f *fakeStore) FindSimilarUsers(ctx context.Context, arg store.FindSimilarUsersParams) ([]store.FindSimilarUsersRow, error) {
if f.findSimilarErr != nil {
return nil, f.findSimilarErr
}
return f.similarRows, nil
}
func (f *fakeStore) PruneStylePosts(ctx context.Context) error {
return nil
}