fix: keep growth analysis available per terminal
This commit is contained in:
@@ -56,6 +56,85 @@ func TestRepositoryChecksUncachedAllowanceBeforeInsert(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositoryAllowsManualRefreshUntilAISummaryExists(t *testing.T) {
|
||||
pool, merchantID := growthRepositoryTestPool(t)
|
||||
repository := NewRepository(pool)
|
||||
ctx := context.Background()
|
||||
start := time.Now().UTC()
|
||||
refreshDay := tehranMidnight(start)
|
||||
newInput := func(id, fingerprint string) CreateRecord {
|
||||
return CreateRecord{ID: id, MerchantID: merchantID, Fingerprint: fingerprint, DatasetWatermark: "test", ForecastStartDate: &start, CatalogVersion: "test", AnalyticsVersion: "test", PromptVersion: "test", SchemaVersion: "test", Model: "test", ManualRefresh: true, RefreshDay: &refreshDay}
|
||||
}
|
||||
|
||||
first, _, err := repository.CreateOrReuse(ctx, newInput("growth_aaaaaaaaaaaaaaaaaaaaaaaa", "first"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if next, err := repository.NextRefreshAt(ctx, merchantID, "", start); err != nil || next.After(start) {
|
||||
t.Fatalf("refresh without AI summary should remain available: next=%s error=%v", next, err)
|
||||
}
|
||||
second, _, err := repository.CreateOrReuse(ctx, newInput("growth_bbbbbbbbbbbbbbbbbbbbbbbb", "second"), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("second refresh without AI summary should be allowed: %v", err)
|
||||
}
|
||||
if _, err := pool.Exec(ctx, `UPDATE growth_reports SET status='READY',ai_response=$2 WHERE id=$1`, second.ReportID, `{"briefs":[{"headline":"خلاصه هوشمند"}]}`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, _, err = repository.CreateOrReuse(ctx, newInput("growth_cccccccccccccccccccccccc", "third"), nil)
|
||||
if !errors.Is(err, ErrRefreshLimited) {
|
||||
t.Fatalf("refresh after AI summary error=%v, want ErrRefreshLimited (first=%s)", err, first.ReportID)
|
||||
}
|
||||
if next, err := repository.NextRefreshAt(ctx, merchantID, "", start); err != nil || !next.After(start) {
|
||||
t.Fatalf("refresh after AI summary should have a future boundary: next=%s error=%v", next, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositoryLatestIgnoresReportsFromOlderAnalyticsVersions(t *testing.T) {
|
||||
pool, merchantID := growthRepositoryTestPool(t)
|
||||
repository := NewRepository(pool)
|
||||
ctx := context.Background()
|
||||
now := time.Now().UTC()
|
||||
|
||||
_, err := pool.Exec(ctx, `INSERT INTO growth_reports(id,merchant_id,fingerprint,status,forecast_start_date,dataset_watermark,catalog_version,analytics_version,prompt_version,schema_version,model,deterministic_evidence,created_at,updated_at) VALUES($1,$2,'old-fingerprint','PARTIAL',current_date,'test','test','1.1.0','test','test','test',$3,$4,$4)`, "growth_dddddddddddddddddddddddd", merchantID, `{"forecast":{"confidence":"INSUFFICIENT_DATA","points":[]}}`, now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := repository.Latest(ctx, merchantID, "", "1.2.0"); !errors.Is(err, ErrNotFound) {
|
||||
t.Fatalf("Latest error=%v, want ErrNotFound for a stale analytics version", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositoryRefreshQuotaIsScopedPerTerminal(t *testing.T) {
|
||||
pool, merchantID := growthRepositoryTestPool(t)
|
||||
repository := NewRepository(pool)
|
||||
ctx := context.Background()
|
||||
start := time.Now().UTC()
|
||||
refreshDay := tehranMidnight(start)
|
||||
for _, terminal := range []string{"terminal-a", "terminal-b"} {
|
||||
if _, err := pool.Exec(ctx, `INSERT INTO terminals(merchant_id,source_key,verify_type) VALUES($1,$2,'test')`, merchantID, terminal); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
newInput := func(id, terminal string) CreateRecord {
|
||||
return CreateRecord{ID: id, MerchantID: merchantID, TerminalID: terminal, Fingerprint: id, DatasetWatermark: "test", ForecastStartDate: &start, CatalogVersion: "test", AnalyticsVersion: "test", PromptVersion: "test", SchemaVersion: "test", Model: "test", ManualRefresh: true, RefreshDay: &refreshDay}
|
||||
}
|
||||
|
||||
first, _, err := repository.CreateOrReuse(ctx, newInput("growth_eeeeeeeeeeeeeeeeeeeeeeee", "terminal-a"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := pool.Exec(ctx, `UPDATE growth_reports SET status='READY',ai_response=$2 WHERE id=$1`, first.ReportID, `{"briefs":[{"headline":"خلاصه هوشمند"}]}`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, _, err := repository.CreateOrReuse(ctx, newInput("growth_ffffffffffffffffffffffff", "terminal-a"), nil); !errors.Is(err, ErrRefreshLimited) {
|
||||
t.Fatalf("same-terminal refresh error=%v, want ErrRefreshLimited", err)
|
||||
}
|
||||
if _, _, err := repository.CreateOrReuse(ctx, newInput("growth_111111111111111111111111", "terminal-b"), nil); err != nil {
|
||||
t.Fatalf("another terminal must retain its own refresh quota: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func growthRepositoryTestPool(t *testing.T) (*pgxpool.Pool, int64) {
|
||||
t.Helper()
|
||||
databaseURL := os.Getenv("TEST_DATABASE_URL")
|
||||
@@ -74,6 +153,7 @@ func growthRepositoryTestPool(t *testing.T) (*pgxpool.Pool, int64) {
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_, _ = pool.Exec(context.Background(), `DELETE FROM growth_reports WHERE merchant_id=$1`, merchantID)
|
||||
_, _ = pool.Exec(context.Background(), `DELETE FROM terminals WHERE merchant_id=$1`, merchantID)
|
||||
_, _ = pool.Exec(context.Background(), `DELETE FROM merchants WHERE id=$1`, merchantID)
|
||||
pool.Close()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user