first commit of restructured project
This commit is contained in:
150
trahn-trade-backend/internal/scheduler/sr_test.go
Normal file
150
trahn-trade-backend/internal/scheduler/sr_test.go
Normal file
@@ -0,0 +1,150 @@
|
||||
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")
|
||||
}
|
||||
Reference in New Issue
Block a user