151 lines
3.9 KiB
Go
151 lines
3.9 KiB
Go
package scheduler_test
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/kjannette/trahn-backend/internal/external"
|
|
"github.com/kjannette/trahn-backend/internal/repository"
|
|
"github.com/kjannette/trahn-backend/internal/scheduler"
|
|
"github.com/kjannette/trahn-backend/internal/strategy"
|
|
"github.com/kjannette/trahn-backend/internal/testutil"
|
|
)
|
|
|
|
func TestSRScheduler_FetchNow(t *testing.T) {
|
|
apiKey := os.Getenv("DUNE_API_KEY")
|
|
if apiKey == "" {
|
|
t.Skip("DUNE_API_KEY not set, skipping")
|
|
}
|
|
|
|
pool := testutil.SetupPool(t)
|
|
srRepo := repository.NewSRRepo(pool)
|
|
dune := external.NewDuneClient(apiKey, external.DuneOptions{
|
|
Method: "simple",
|
|
LookbackDays: 14,
|
|
RefreshHours: 48,
|
|
})
|
|
|
|
var srUpdated atomic.Bool
|
|
var recalculated atomic.Bool
|
|
|
|
sched := scheduler.NewSRScheduler(dune, srRepo, scheduler.SRSchedulerConfig{
|
|
CronInterval: 1 * time.Hour,
|
|
SRChangeThreshold: 5,
|
|
OnSRUpdate: func(sr *external.SRResult) {
|
|
srUpdated.Store(true)
|
|
},
|
|
OnGridRecalculate: func(sr *external.SRResult) {
|
|
recalculated.Store(true)
|
|
},
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
|
|
defer cancel()
|
|
|
|
err := sched.FetchNow(ctx)
|
|
if err != nil {
|
|
t.Fatalf("FetchNow: %v", err)
|
|
}
|
|
|
|
if !srUpdated.Load() {
|
|
t.Fatal("OnSRUpdate callback was not called")
|
|
}
|
|
t.Log("OnSRUpdate: called")
|
|
t.Logf("OnGridRecalculate called: %v", recalculated.Load())
|
|
|
|
// Verify it was stored in DB
|
|
latest, err := srRepo.GetLatest(ctx)
|
|
if err != nil {
|
|
t.Fatalf("GetLatest: %v", err)
|
|
}
|
|
if latest == nil {
|
|
t.Fatal("expected S/R record in DB")
|
|
}
|
|
t.Logf("DB record: support=$%.2f resistance=$%.2f midpoint=$%.2f recalc=%v",
|
|
latest.Support, latest.Resistance, latest.Midpoint, latest.GridRecalculated)
|
|
}
|
|
|
|
func TestSRScheduler_WithBotState_PriceOutside(t *testing.T) {
|
|
apiKey := os.Getenv("DUNE_API_KEY")
|
|
if apiKey == "" {
|
|
t.Skip("DUNE_API_KEY not set, skipping")
|
|
}
|
|
|
|
pool := testutil.SetupPool(t)
|
|
srRepo := repository.NewSRRepo(pool)
|
|
dune := external.NewDuneClient(apiKey, external.DuneOptions{
|
|
Method: "simple",
|
|
LookbackDays: 14,
|
|
RefreshHours: 48,
|
|
})
|
|
|
|
var recalculated atomic.Bool
|
|
var recalcReasons string
|
|
|
|
// Simulate a grid that is far from current market price
|
|
fakeBotState := &scheduler.BotState{
|
|
Grid: []strategy.GridLevel{
|
|
{Index: 0, Price: 1000, Side: "buy"},
|
|
{Index: 1, Price: 1050, Side: "buy"},
|
|
{Index: 2, Price: 1100, Side: "sell"},
|
|
{Index: 3, Price: 1150, Side: "sell"},
|
|
},
|
|
LastETHPrice: 1962, // current real price, way outside the grid
|
|
}
|
|
|
|
sched := scheduler.NewSRScheduler(dune, srRepo, scheduler.SRSchedulerConfig{
|
|
CronInterval: 1 * time.Hour,
|
|
SRChangeThreshold: 50, // high threshold so only bot state triggers recalc
|
|
GetBotState: func() *scheduler.BotState {
|
|
return fakeBotState
|
|
},
|
|
OnGridRecalculate: func(sr *external.SRResult) {
|
|
recalculated.Store(true)
|
|
recalcReasons = "price outside grid"
|
|
},
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
|
|
defer cancel()
|
|
|
|
err := sched.FetchNow(ctx)
|
|
if err != nil {
|
|
t.Fatalf("FetchNow: %v", err)
|
|
}
|
|
|
|
if !recalculated.Load() {
|
|
t.Fatal("expected recalculation due to price outside grid")
|
|
}
|
|
t.Logf("Recalculated: true (reason: %s)", recalcReasons)
|
|
}
|
|
|
|
func TestSRScheduler_StartStop(t *testing.T) {
|
|
pool := testutil.SetupPool(t)
|
|
srRepo := repository.NewSRRepo(pool)
|
|
|
|
// Use a dummy Dune client (no API key — won't actually fetch)
|
|
dune := external.NewDuneClient("", external.DuneOptions{})
|
|
|
|
sched := scheduler.NewSRScheduler(dune, srRepo, scheduler.SRSchedulerConfig{
|
|
CronInterval: 1 * time.Hour,
|
|
})
|
|
|
|
sched.Start()
|
|
if !sched.Running() {
|
|
t.Fatal("expected running after Start")
|
|
}
|
|
|
|
// Give initial goroutine a moment (it will fail due to no API key, that's fine)
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
sched.Stop()
|
|
if sched.Running() {
|
|
t.Fatal("expected not running after Stop")
|
|
}
|
|
|
|
t.Log("Start/Stop lifecycle: OK")
|
|
}
|