Files
2026-02-22 15:21:18 -05:00

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")
}