package service import ( "context" "errors" "testing" "time" appconfig "github.com/D36u99er/bc-netts-energy/internal/config" "github.com/D36u99er/bc-netts-energy/internal/netts" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "log/slog" ) func TestEnsureEnergyAddsAddressAndOrdersCycles(t *testing.T) { mock := &mockNettsClient{ analyzeData: &netts.AnalyzeUSDTData{ TransferDetails: netts.TransferDetails{ RecommendedEnergy: 131000, EnergyNeeded: 131000, }, }, statusResponses: []statusResponse{ {err: netts.ErrAddressNotFound}, {status: &netts.AddressStatus{CyclesRemaining: 2}}, {status: &netts.AddressStatus{CyclesRemaining: 5}}, }, orderResult: &netts.OrderResult{ CyclesPurchased: 3, TotalCycles: 5, OrderID: "ORD-123", Status: "confirmed", TotalCost: 7.5, }, } cfg := appconfig.EnergyConfig{ AutoAddHost: true, MinCycles: 3, TargetCycles: 5, PostOrderWait: appconfig.Duration(0), DefaultAnalyzeValue: "100.00", } logger := slog.New(slog.NewTextHandler(ioDiscard{}, &slog.HandlerOptions{Level: slog.LevelDebug})) svc := NewEnergyService(cfg, mock, logger) resp, err := svc.EnsureEnergy(context.Background(), EnsureEnergyRequest{ FromAddress: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE", ToAddress: "TUmyHQNzAkT6SrwThVvay7G4yfGZAyWhmy", Amount: "50.0", }) require.NoError(t, err) assert.True(t, resp.AddressAdded) assert.Equal(t, 3, resp.CyclesPurchased) assert.Equal(t, 5, resp.CyclesAfter) assert.Equal(t, "ORD-123", resp.OrderID) assert.Equal(t, 131000, resp.RecommendedEnergy) assert.Equal(t, 131000, resp.EnergyNeeded) assert.Equal(t, 1, mock.addCalls) assert.Equal(t, []int{3}, mock.orderCalls) assert.Equal(t, 0, len(mock.statusResponses)) } func TestEnsureEnergyFailsWhenAutoAddDisabled(t *testing.T) { mock := &mockNettsClient{ analyzeData: &netts.AnalyzeUSDTData{TransferDetails: netts.TransferDetails{RecommendedEnergy: 1000}}, statusResponses: []statusResponse{ {err: netts.ErrAddressNotFound}, }, } cfg := appconfig.EnergyConfig{ AutoAddHost: false, MinCycles: 2, TargetCycles: 4, } logger := slog.New(slog.NewTextHandler(ioDiscard{}, nil)) svc := NewEnergyService(cfg, mock, logger) _, err := svc.EnsureEnergy(context.Background(), EnsureEnergyRequest{ FromAddress: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE", ToAddress: "TUmyHQNzAkT6SrwThVvay7G4yfGZAyWhmy", }) require.Error(t, err) assert.Equal(t, 0, mock.addCalls) } func TestEnsureEnergySkipsOrderWhenSufficientCycles(t *testing.T) { mock := &mockNettsClient{ analyzeData: &netts.AnalyzeUSDTData{ TransferDetails: netts.TransferDetails{ RecommendedEnergy: 90000, EnergyNeeded: 80000, }, }, statusResponses: []statusResponse{ {status: &netts.AddressStatus{CyclesRemaining: 5}}, }, } cfg := appconfig.EnergyConfig{ AutoAddHost: true, MinCycles: 3, TargetCycles: 6, } logger := slog.New(slog.NewTextHandler(ioDiscard{}, nil)) svc := NewEnergyService(cfg, mock, logger) resp, err := svc.EnsureEnergy(context.Background(), EnsureEnergyRequest{ FromAddress: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE", ToAddress: "TUmyHQNzAkT6SrwThVvay7G4yfGZAyWhmy", }) require.NoError(t, err) assert.False(t, resp.AddressAdded) assert.Equal(t, 0, resp.CyclesPurchased) assert.Equal(t, 5, resp.CyclesAfter) assert.Empty(t, mock.orderCalls) } type statusResponse struct { status *netts.AddressStatus err error } type mockNettsClient struct { analyzeData *netts.AnalyzeUSDTData statusResponses []statusResponse orderResult *netts.OrderResult addCalls int orderCalls []int } func (m *mockNettsClient) AnalyzeUSDT(ctx context.Context, req netts.AnalyzeUSDTRequest) (*netts.AnalyzeUSDTData, error) { if m.analyzeData == nil { return nil, errors.New("no analysis data") } return m.analyzeData, nil } func (m *mockNettsClient) GetAddressStatus(ctx context.Context, address string) (*netts.AddressStatus, error) { if len(m.statusResponses) == 0 { return nil, errors.New("no status response") } resp := m.statusResponses[0] m.statusResponses = m.statusResponses[1:] return resp.status, resp.err } func (m *mockNettsClient) AddHostAddress(ctx context.Context, address, callback string) (*netts.AddAddressResult, error) { m.addCalls++ return &netts.AddAddressResult{ Address: address, CallbackURL: callback, Timestamp: time.Now().UTC().Format(time.RFC3339), }, nil } func (m *mockNettsClient) OrderCycles(ctx context.Context, address string, cycles int) (*netts.OrderResult, error) { m.orderCalls = append(m.orderCalls, cycles) if m.orderResult != nil { return m.orderResult, nil } return &netts.OrderResult{ CyclesPurchased: cycles, TotalCycles: cycles, OrderID: "ORD", Status: "confirmed", }, nil } type ioDiscard struct{} func (ioDiscard) Write(p []byte) (int, error) { return len(p), nil }