320 lines
8.0 KiB
Go
320 lines
8.0 KiB
Go
package repository_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/kjannette/trahn-backend/internal/models"
|
|
"github.com/kjannette/trahn-backend/internal/repository"
|
|
"github.com/kjannette/trahn-backend/internal/testutil"
|
|
)
|
|
|
|
// ---------- PriceRepo ----------
|
|
|
|
func TestPriceRepo(t *testing.T) {
|
|
pool := testutil.SetupPool(t)
|
|
repo := repository.NewPriceRepo(pool)
|
|
ctx := context.Background()
|
|
|
|
// Record
|
|
ts := time.Now()
|
|
p, err := repo.Record(ctx, 2650.42, ts)
|
|
if err != nil {
|
|
t.Fatalf("Record: %v", err)
|
|
}
|
|
if p.ID == 0 {
|
|
t.Fatal("expected non-zero ID")
|
|
}
|
|
if p.Price != 2650.42 {
|
|
t.Fatalf("price mismatch: got %f", p.Price)
|
|
}
|
|
t.Logf("Recorded price: id=%d price=%.2f day=%s", p.ID, p.Price, p.TradingDay)
|
|
|
|
// GetLatest
|
|
latest, err := repo.GetLatest(ctx)
|
|
if err != nil {
|
|
t.Fatalf("GetLatest: %v", err)
|
|
}
|
|
if latest == nil {
|
|
t.Fatal("expected latest price")
|
|
}
|
|
t.Logf("Latest: id=%d price=%.2f", latest.ID, latest.Price)
|
|
|
|
// GetByDay
|
|
prices, err := repo.GetByDay(ctx, p.TradingDay)
|
|
if err != nil {
|
|
t.Fatalf("GetByDay: %v", err)
|
|
}
|
|
if len(prices) == 0 {
|
|
t.Fatal("expected prices for trading day")
|
|
}
|
|
t.Logf("GetByDay(%s): %d rows", p.TradingDay, len(prices))
|
|
|
|
// GetAvailableDays
|
|
days, err := repo.GetAvailableDays(ctx)
|
|
if err != nil {
|
|
t.Fatalf("GetAvailableDays: %v", err)
|
|
}
|
|
if len(days) == 0 {
|
|
t.Fatal("expected at least one day")
|
|
}
|
|
t.Logf("Available days: %v", days)
|
|
}
|
|
|
|
// ---------- TradeRepo ----------
|
|
|
|
func TestTradeRepo(t *testing.T) {
|
|
pool := testutil.SetupPool(t)
|
|
repo := repository.NewTradeRepo(pool)
|
|
ctx := context.Background()
|
|
|
|
slippage := 0.35
|
|
gasCost := 0.002
|
|
gridLvl := 3
|
|
|
|
trade := &models.Trade{
|
|
Timestamp: time.Now(),
|
|
Side: "buy",
|
|
Price: 2600.00,
|
|
Quantity: 0.0385,
|
|
USDValue: 100.00,
|
|
GridLevel: &gridLvl,
|
|
IsPaperTrade: true,
|
|
SlippagePercent: &slippage,
|
|
GasCostETH: &gasCost,
|
|
}
|
|
|
|
recorded, err := repo.Record(ctx, trade)
|
|
if err != nil {
|
|
t.Fatalf("Record: %v", err)
|
|
}
|
|
if recorded.ID == 0 {
|
|
t.Fatal("expected non-zero ID")
|
|
}
|
|
if recorded.Side != "buy" {
|
|
t.Fatalf("side mismatch: got %s", recorded.Side)
|
|
}
|
|
t.Logf("Recorded trade: id=%d side=%s price=%.2f qty=%.4f", recorded.ID, recorded.Side, recorded.Price, recorded.Quantity)
|
|
|
|
// GetAll (no filter)
|
|
all, err := repo.GetAll(ctx, 10, nil)
|
|
if err != nil {
|
|
t.Fatalf("GetAll: %v", err)
|
|
}
|
|
if len(all) == 0 {
|
|
t.Fatal("expected trades")
|
|
}
|
|
t.Logf("GetAll: %d trades", len(all))
|
|
|
|
// GetAll (paper only)
|
|
paperMode := true
|
|
paperTrades, err := repo.GetAll(ctx, 10, &paperMode)
|
|
if err != nil {
|
|
t.Fatalf("GetAll(paper): %v", err)
|
|
}
|
|
for _, pt := range paperTrades {
|
|
if !pt.IsPaperTrade {
|
|
t.Fatalf("expected paper trade, got live trade id=%d", pt.ID)
|
|
}
|
|
}
|
|
t.Logf("GetAll(paper): %d trades", len(paperTrades))
|
|
|
|
// GetStats (no filter)
|
|
stats, err := repo.GetStats(ctx, nil)
|
|
if err != nil {
|
|
t.Fatalf("GetStats: %v", err)
|
|
}
|
|
t.Logf("Stats(all): total=%d buys=%d sells=%d", stats.TotalTrades, stats.BuyCount, stats.SellCount)
|
|
|
|
// GetStats (paper only)
|
|
paperStats, err := repo.GetStats(ctx, &paperMode)
|
|
if err != nil {
|
|
t.Fatalf("GetStats(paper): %v", err)
|
|
}
|
|
t.Logf("Stats(paper): total=%d buys=%d sells=%d", paperStats.TotalTrades, paperStats.BuyCount, paperStats.SellCount)
|
|
}
|
|
|
|
// ---------- SRRepo ----------
|
|
|
|
func TestSRRepo(t *testing.T) {
|
|
pool := testutil.SetupPool(t)
|
|
repo := repository.NewSRRepo(pool)
|
|
ctx := context.Background()
|
|
|
|
avgPrice := 2700.0
|
|
sr := &models.SupportResistance{
|
|
Timestamp: time.Now(),
|
|
Method: "simple",
|
|
LookbackDays: 14,
|
|
Support: 2400.00,
|
|
Resistance: 3000.00,
|
|
Midpoint: 2700.00,
|
|
AvgPrice: &avgPrice,
|
|
GridRecalculated: false,
|
|
}
|
|
|
|
recorded, err := repo.Record(ctx, sr)
|
|
if err != nil {
|
|
t.Fatalf("Record: %v", err)
|
|
}
|
|
if recorded.ID == 0 {
|
|
t.Fatal("expected non-zero ID")
|
|
}
|
|
t.Logf("Recorded S/R: id=%d support=%.2f resistance=%.2f mid=%.2f", recorded.ID, recorded.Support, recorded.Resistance, recorded.Midpoint)
|
|
|
|
// GetLatest
|
|
latest, err := repo.GetLatest(ctx)
|
|
if err != nil {
|
|
t.Fatalf("GetLatest: %v", err)
|
|
}
|
|
if latest == nil {
|
|
t.Fatal("expected latest S/R")
|
|
}
|
|
t.Logf("Latest S/R: mid=%.2f", latest.Midpoint)
|
|
|
|
// GetHistory
|
|
history, err := repo.GetHistory(ctx, 10)
|
|
if err != nil {
|
|
t.Fatalf("GetHistory: %v", err)
|
|
}
|
|
t.Logf("S/R history: %d rows", len(history))
|
|
|
|
// NeedsRefresh (just recorded, so should NOT need refresh)
|
|
needs, err := repo.NeedsRefresh(ctx, 48)
|
|
if err != nil {
|
|
t.Fatalf("NeedsRefresh: %v", err)
|
|
}
|
|
if needs {
|
|
t.Fatal("should NOT need refresh right after recording")
|
|
}
|
|
t.Logf("NeedsRefresh(48h): %v", needs)
|
|
|
|
// CheckSignificantChange
|
|
shifted := &models.SupportResistance{Midpoint: 2900.00}
|
|
analysis, err := repo.CheckSignificantChange(ctx, shifted, 5)
|
|
if err != nil {
|
|
t.Fatalf("CheckSignificantChange: %v", err)
|
|
}
|
|
t.Logf("Change analysis: changed=%v reason=%s", analysis.HasChanged, analysis.Reason)
|
|
}
|
|
|
|
// ---------- GridStateRepo ----------
|
|
|
|
func TestGridStateRepo(t *testing.T) {
|
|
pool := testutil.SetupPool(t)
|
|
repo := repository.NewGridStateRepo(pool)
|
|
ctx := context.Background()
|
|
|
|
basePrice := 2700.0
|
|
levels := json.RawMessage(`[{"index":0,"price":2600,"side":"buy","filled":false}]`)
|
|
|
|
gs := &models.GridState{
|
|
BasePrice: &basePrice,
|
|
GridLevelsJSON: levels,
|
|
TradesExecuted: 0,
|
|
TotalProfit: 0,
|
|
}
|
|
|
|
saved, err := repo.Save(ctx, gs)
|
|
if err != nil {
|
|
t.Fatalf("Save: %v", err)
|
|
}
|
|
if saved.ID == 0 {
|
|
t.Fatal("expected non-zero ID")
|
|
}
|
|
if !saved.IsActive {
|
|
t.Fatal("expected active state")
|
|
}
|
|
t.Logf("Saved grid state: id=%d active=%v", saved.ID, saved.IsActive)
|
|
|
|
// GetActive
|
|
active, err := repo.GetActive(ctx)
|
|
if err != nil {
|
|
t.Fatalf("GetActive: %v", err)
|
|
}
|
|
if active == nil {
|
|
t.Fatal("expected active state")
|
|
}
|
|
t.Logf("Active grid state: id=%d", active.ID)
|
|
|
|
// UpdateGridLevels
|
|
newLevels := json.RawMessage(`[{"index":0,"price":2600,"side":"buy","filled":true}]`)
|
|
if err := repo.UpdateGridLevels(ctx, newLevels); err != nil {
|
|
t.Fatalf("UpdateGridLevels: %v", err)
|
|
}
|
|
t.Log("UpdateGridLevels: OK")
|
|
|
|
// UpdateTradeStats
|
|
if err := repo.UpdateTradeStats(ctx, 5, 12.50); err != nil {
|
|
t.Fatalf("UpdateTradeStats: %v", err)
|
|
}
|
|
t.Log("UpdateTradeStats: OK")
|
|
|
|
// InitializePaperWallet
|
|
if err := repo.InitializePaperWallet(ctx, 1.0, 1000.0); err != nil {
|
|
t.Fatalf("InitializePaperWallet: %v", err)
|
|
}
|
|
t.Log("InitializePaperWallet: OK")
|
|
|
|
// GetPaperWallet
|
|
pw, err := repo.GetPaperWallet(ctx)
|
|
if err != nil {
|
|
t.Fatalf("GetPaperWallet: %v", err)
|
|
}
|
|
if pw == nil {
|
|
t.Fatal("expected paper wallet")
|
|
}
|
|
if pw.ETHBalance != 1.0 {
|
|
t.Fatalf("ETH balance mismatch: got %f", pw.ETHBalance)
|
|
}
|
|
t.Logf("PaperWallet: ETH=%.4f USDC=%.2f", pw.ETHBalance, pw.USDCBalance)
|
|
|
|
// UpdatePaperWallet
|
|
pw.ETHBalance = 1.05
|
|
pw.USDCBalance = 870.00
|
|
pw.TotalGasSpent = 0.003
|
|
if err := repo.UpdatePaperWallet(ctx, pw); err != nil {
|
|
t.Fatalf("UpdatePaperWallet: %v", err)
|
|
}
|
|
t.Log("UpdatePaperWallet: OK")
|
|
|
|
// Verify update
|
|
pw2, err := repo.GetPaperWallet(ctx)
|
|
if err != nil {
|
|
t.Fatalf("GetPaperWallet after update: %v", err)
|
|
}
|
|
if pw2.ETHBalance != 1.05 {
|
|
t.Fatalf("ETH balance mismatch after update: got %f", pw2.ETHBalance)
|
|
}
|
|
t.Logf("PaperWallet after update: ETH=%.4f USDC=%.2f gas=%.4f", pw2.ETHBalance, pw2.USDCBalance, pw2.TotalGasSpent)
|
|
|
|
// GetHistory
|
|
history, err := repo.GetHistory(ctx, 10)
|
|
if err != nil {
|
|
t.Fatalf("GetHistory: %v", err)
|
|
}
|
|
t.Logf("Grid state history: %d rows", len(history))
|
|
}
|
|
|
|
// ---------- TradingDay ----------
|
|
|
|
func TestTradingDay(t *testing.T) {
|
|
// 2024-01-15 at 16:00 UTC (before 17:00 cutoff) => trading day = Jan 14
|
|
ts := time.Date(2024, 1, 15, 16, 0, 0, 0, time.UTC)
|
|
got := repository.TradingDay(ts)
|
|
if got != "2024-01-14" {
|
|
t.Fatalf("expected 2024-01-14, got %s", got)
|
|
}
|
|
|
|
// 2024-01-15 at 18:00 UTC (after 17:00 cutoff) => trading day = Jan 15
|
|
ts2 := time.Date(2024, 1, 15, 18, 0, 0, 0, time.UTC)
|
|
got2 := repository.TradingDay(ts2)
|
|
if got2 != "2024-01-15" {
|
|
t.Fatalf("expected 2024-01-15, got %s", got2)
|
|
}
|
|
|
|
t.Logf("TradingDay tests passed")
|
|
}
|