Add tests for in-memory time series

This commit is contained in:
Taras Kushnir
2026-01-06 18:48:08 +02:00
parent 5a4abcb3e5
commit 2435770e2f

243
pkg/db/timeseries_test.go Normal file
View File

@@ -0,0 +1,243 @@
package db
import (
"context"
"testing"
"time"
"github.com/PrivateCaptcha/PrivateCaptcha/pkg/common"
)
func TestMemoryTimeSeriesPing(t *testing.T) {
ts := NewMemoryTimeSeries()
if err := ts.Ping(context.Background()); err != nil {
t.Error(err)
}
}
func TestMemoryTimeSeriesRetrievePropertyStatsSince(t *testing.T) {
ts := NewMemoryTimeSeries()
ctx := context.Background()
// Use a fixed time aligned to 5 minutes to ensure deterministic bucketing
now := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC)
records := []*common.AccessRecord{
{
UserID: 1,
OrgID: 10,
PropertyID: 100,
Timestamp: now,
},
{
UserID: 1,
OrgID: 10,
PropertyID: 100,
Timestamp: now.Add(1 * time.Minute),
},
{
UserID: 1,
OrgID: 10,
PropertyID: 100,
Timestamp: now.Add(6 * time.Minute), // Different 5m bucket
},
{
UserID: 2,
OrgID: 10,
PropertyID: 100,
Timestamp: now,
},
}
err := ts.WriteAccessLogBatch(ctx, records)
if err != nil {
t.Fatal(err)
}
req := &common.BackfillRequest{
UserID: 1,
OrgID: 10,
PropertyID: 100,
}
// Expecting 2 buckets for User 1.
// Bucket 1: now (truncated to 5m) -> count 2
// Bucket 2: now+6m (truncated to 5m) -> count 1
stats, err := ts.RetrievePropertyStatsSince(ctx, req, now.Add(-1*time.Hour))
if err != nil {
t.Error(err)
}
if actual := len(stats); actual != 2 {
t.Errorf("RetrievePropertyStatsSince() got %d stats, want 2", actual)
}
totalCount := uint32(0)
for _, s := range stats {
totalCount += s.Count
}
if totalCount != 3 {
t.Errorf("RetrievePropertyStatsSince() total count = %d, want 3", totalCount)
}
}
func TestMemoryTimeSeriesRetrieveAccountStats(t *testing.T) {
// Aggregates by month
ts := NewMemoryTimeSeries()
ctx := context.Background()
fixedTime := time.Date(2023, 10, 15, 12, 0, 0, 0, time.UTC)
records := []*common.AccessRecord{
{UserID: 1, Timestamp: fixedTime},
{UserID: 1, Timestamp: fixedTime.Add(24 * time.Hour)},
{UserID: 2, Timestamp: fixedTime},
}
ts.WriteAccessLogBatch(ctx, records)
accountStats, err := ts.RetrieveAccountStats(ctx, 1, fixedTime.Add(-24*time.Hour))
if err != nil {
t.Error(err)
}
if len(accountStats) != 1 {
t.Errorf("RetrieveAccountStats() got %d stats, want 1", len(accountStats))
} else {
if accountStats[0].Count != 2 {
t.Errorf("RetrieveAccountStats() count = %d, want 2", accountStats[0].Count)
}
expectedTs := time.Date(2023, 10, 1, 0, 0, 0, 0, time.UTC)
if !accountStats[0].Timestamp.Equal(expectedTs) {
t.Errorf("RetrieveAccountStats() timestamp = %v, want %v", accountStats[0].Timestamp, expectedTs)
}
}
}
func TestMemoryTimeSeriesVerifyLogsAndStatsByPeriod(t *testing.T) {
ts := NewMemoryTimeSeries()
ctx := context.Background()
now := time.Now().UTC()
accessRecords := []*common.AccessRecord{
{OrgID: 1, PropertyID: 1, Timestamp: now.Add(-30 * time.Minute)}, // Today
{OrgID: 1, PropertyID: 1, Timestamp: now.Add(-2 * time.Hour)}, // Today
}
ts.WriteAccessLogBatch(ctx, accessRecords)
verifyRecords := []*common.VerifyRecord{
{OrgID: 1, PropertyID: 1, Timestamp: now.Add(-30 * time.Minute), Status: 1},
}
ts.WriteVerifyLogBatch(ctx, verifyRecords)
stats, err := ts.RetrievePropertyStatsByPeriod(ctx, 1, 1, common.TimePeriodToday)
if err != nil {
t.Error(err)
}
totalReq := 0
totalVer := 0
for _, s := range stats {
totalReq += s.RequestsCount
totalVer += s.VerifiesCount
}
if totalReq != 2 {
t.Errorf("RetrievePropertyStatsByPeriod(Today) requests = %d, want 2", totalReq)
}
if totalVer != 1 {
t.Errorf("RetrievePropertyStatsByPeriod(Today) verifies = %d, want 1", totalVer)
}
}
func TestMemoryTimeSeriesRecentTopProperties(t *testing.T) {
ts := NewMemoryTimeSeries()
ctx := context.Background()
now := time.Now().UTC()
records := []*common.VerifyRecord{
{PropertyID: 1, Timestamp: now},
{PropertyID: 1, Timestamp: now},
{PropertyID: 2, Timestamp: now},
{PropertyID: 3, Timestamp: now.Add(-48 * time.Hour)}, // Too old
}
ts.WriteVerifyLogBatch(ctx, records)
top, err := ts.RetrieveRecentTopProperties(ctx, 10)
if err != nil {
t.Error(err)
}
if len(top) != 2 {
t.Errorf("RetrieveRecentTopProperties() got %d properties, want 2", len(top))
}
if top[1] != 2 {
t.Errorf("Property 1 count = %d, want 2", top[1])
}
if top[2] != 1 {
t.Errorf("Property 2 count = %d, want 1", top[2])
}
if _, ok := top[3]; ok {
t.Errorf("Property 3 should not be in top list (too old)")
}
}
func TestMemoryTimeSeriesDeletePropertiesData(t *testing.T) {
ts := NewMemoryTimeSeries()
ctx := context.Background()
// Populate
ts.WriteAccessLogBatch(ctx, []*common.AccessRecord{
{UserID: 1, OrgID: 10, PropertyID: 100},
{UserID: 2, OrgID: 20, PropertyID: 200},
{UserID: 3, OrgID: 30, PropertyID: 300},
})
ts.WriteVerifyLogBatch(ctx, []*common.VerifyRecord{
{UserID: 1, OrgID: 10, PropertyID: 100},
{UserID: 2, OrgID: 20, PropertyID: 200},
{UserID: 3, OrgID: 30, PropertyID: 300},
})
if err := ts.DeletePropertiesData(ctx, []int32{100}); err != nil {
t.Error(err)
}
stats, _ := ts.RetrievePropertyStatsSince(ctx, &common.BackfillRequest{UserID: 1, OrgID: 10, PropertyID: 100}, time.Time{})
if len(stats) != 0 {
t.Errorf("After DeletePropertiesData, stats count = %d, want 0", len(stats))
}
}
func TestMemoryTimeSeriesDeleteAccountData(t *testing.T) {
ts := NewMemoryTimeSeries()
ctx := context.Background()
// Populate
ts.WriteAccessLogBatch(ctx, []*common.AccessRecord{
{UserID: 1, OrgID: 10, PropertyID: 100},
{UserID: 2, OrgID: 20, PropertyID: 200},
{UserID: 3, OrgID: 30, PropertyID: 300},
})
ts.WriteVerifyLogBatch(ctx, []*common.VerifyRecord{
{UserID: 1, OrgID: 10, PropertyID: 100},
{UserID: 2, OrgID: 20, PropertyID: 200},
{UserID: 3, OrgID: 30, PropertyID: 300},
})
if err := ts.DeleteOrganizationsData(ctx, []int32{20}); err != nil {
t.Error(err)
}
// Check user 2 (Org 20)
stats2, _ := ts.RetrieveAccountStats(ctx, 2, time.Time{})
if len(stats2) != 0 {
t.Errorf("After DeleteOrganizationsData, stats count = %d, want 0", len(stats2))
}
// Delete User 3
if err := ts.DeleteUsersData(ctx, []int32{3}); err != nil {
t.Errorf("DeleteUsersData error = %v", err)
}
// Check user 3
stats3, _ := ts.RetrieveAccountStats(ctx, 3, time.Time{})
if len(stats3) != 0 {
t.Errorf("After DeleteUsersData, stats count = %d, want 0", len(stats3))
}
}