package postgres import ( "testing" "time" "git.toki-labs.com/toki/alt/packages/domain/backtest" "git.toki-labs.com/toki/alt/packages/domain/market" "git.toki-labs.com/toki/alt/services/worker/internal/storage/postgres/sqlc" ) func TestBacktestRunMappingRoundTrip(t *testing.T) { now := time.Now().UTC().Truncate(time.Microsecond) // Truncate because postgres timestamptz resolves to microsecond precision run := backtest.Run{ ID: backtest.RunID("run-123"), Spec: backtest.RunSpec{ StrategyID: backtest.StrategyID("strat-abc"), Market: market.MarketKR, Timeframe: market.TimeframeDaily, From: now.Add(-24 * time.Hour), To: now, }, Status: backtest.RunStatusSucceeded, CreatedAt: now.Add(-25 * time.Hour), UpdatedAt: now, } params, err := mapRunToParams(run) if err != nil { t.Fatalf("failed to map run to params: %v", err) } row := sqlc.BacktestRun{ ID: params.ID, StrategyID: params.StrategyID, Market: params.Market, Timeframe: params.Timeframe, FromTime: params.FromTime, ToTime: params.ToTime, Status: params.Status, CreatedAt: params.CreatedAt, UpdatedAt: params.UpdatedAt, } mappedBack, err := mapRowToRun(row) if err != nil { t.Fatalf("failed to map row back to run: %v", err) } if mappedBack.ID != run.ID { t.Errorf("ID mismatch: got %v, want %v", mappedBack.ID, run.ID) } if mappedBack.Spec.StrategyID != run.Spec.StrategyID { t.Errorf("StrategyID mismatch: got %v, want %v", mappedBack.Spec.StrategyID, run.Spec.StrategyID) } if mappedBack.Spec.Market != run.Spec.Market { t.Errorf("Market mismatch: got %v, want %v", mappedBack.Spec.Market, run.Spec.Market) } if mappedBack.Spec.Timeframe != run.Spec.Timeframe { t.Errorf("Timeframe mismatch: got %v, want %v", mappedBack.Spec.Timeframe, run.Spec.Timeframe) } if !mappedBack.Spec.From.Equal(run.Spec.From) { t.Errorf("From mismatch: got %v, want %v", mappedBack.Spec.From, run.Spec.From) } if !mappedBack.Spec.To.Equal(run.Spec.To) { t.Errorf("To mismatch: got %v, want %v", mappedBack.Spec.To, run.Spec.To) } if mappedBack.Status != run.Status { t.Errorf("Status mismatch: got %v, want %v", mappedBack.Status, run.Status) } if !mappedBack.CreatedAt.Equal(run.CreatedAt) { t.Errorf("CreatedAt mismatch: got %v, want %v", mappedBack.CreatedAt, run.CreatedAt) } if !mappedBack.UpdatedAt.Equal(run.UpdatedAt) { t.Errorf("UpdatedAt mismatch: got %v, want %v", mappedBack.UpdatedAt, run.UpdatedAt) } } func TestMarketBarMappingRejectsInvalidDecimal(t *testing.T) { now := time.Now().UTC() bar := market.Bar{ InstrumentID: market.InstrumentID("KRX:005930"), Timeframe: market.TimeframeDaily, Timestamp: now, Open: market.Price{ Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "invalid_decimal"}, }, High: market.Price{ Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "50000"}, }, Low: market.Price{ Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "49000"}, }, Close: market.Price{ Currency: market.CurrencyKRW, Amount: market.Decimal{Value: "49500"}, }, Volume: market.Quantity{ Amount: market.Decimal{Value: "1000000"}, }, } _, err := mapBarToParams(bar) if err == nil { t.Fatal("expected error when mapping bar with invalid open decimal, got nil") } // Make Open valid but Volume invalid bar.Open.Amount.Value = "50000" bar.Volume.Amount.Value = "abc" _, err = mapBarToParams(bar) if err == nil { t.Fatal("expected error when mapping bar with invalid volume decimal, got nil") } }