Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 72 additions & 64 deletions examples/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

"github.com/recomma/3commas-mock/server"
"github.com/recomma/3commas-mock/tcmock"
)

// TestExampleBasicUsage demonstrates basic usage of the mock server
Expand All @@ -15,38 +16,11 @@ func TestExampleBasicUsage(t *testing.T) {
defer mockServer.Close()

// Configure mock state
mockServer.AddBot(server.Bot{
ID: 1,
Name: "Test Bot",
Enabled: true,
AccountID: 12345,
})

mockServer.AddDeal(1, server.Deal{
ID: 101,
BotID: 1,
Pair: "USDT_BTC",
Status: "active",
ToCurrency: "BTC",
FromCurrency: "USDT",
CreatedAt: "2024-01-15T10:30:00.000Z",
UpdatedAt: "2024-01-15T10:30:00.000Z",
Events: []server.BotEvent{
{
CreatedAt: "2024-01-15T10:30:00.000Z",
Action: "place",
Coin: "BTC",
Type: "buy",
Status: "active",
Price: "50000.0",
Size: "0.0002",
OrderType: "base",
OrderSize: 1,
OrderPosition: 1,
IsMarket: false,
},
},
})
mockServer.AddBot(server.NewBot(1, "Test Bot", 12345, true))

deal := server.NewDeal(101, 1, "USDT_BTC", "active")
server.AddBotEvent(&deal, "Placing base order. Price: 50000.0 USDT Size: 0.0002 BTC")
mockServer.AddDeal(deal)

// Test ListBots
resp, err := http.Get(mockServer.URL() + "/ver1/bots?scope=enabled")
Expand All @@ -55,16 +29,16 @@ func TestExampleBasicUsage(t *testing.T) {
}
defer resp.Body.Close()

var bots []server.Bot
var bots []tcmock.Bot
if err := json.NewDecoder(resp.Body).Decode(&bots); err != nil {
t.Fatalf("failed to decode bots: %v", err)
}

if len(bots) != 1 {
t.Fatalf("expected 1 bot, got %d", len(bots))
}
if bots[0].ID != 1 {
t.Fatalf("expected bot ID 1, got %d", bots[0].ID)
if bots[0].Id != 1 {
t.Fatalf("expected bot ID 1, got %d", bots[0].Id)
}

// Test GetDeal
Expand All @@ -74,16 +48,16 @@ func TestExampleBasicUsage(t *testing.T) {
}
defer resp.Body.Close()

var deal server.Deal
if err := json.NewDecoder(resp.Body).Decode(&deal); err != nil {
var dealResp tcmock.Deal
if err := json.NewDecoder(resp.Body).Decode(&dealResp); err != nil {
t.Fatalf("failed to decode deal: %v", err)
}

if deal.ID != 101 {
t.Fatalf("expected deal ID 101, got %d", deal.ID)
if dealResp.Id != 101 {
t.Fatalf("expected deal ID 101, got %d", dealResp.Id)
}
if len(deal.Events) != 1 {
t.Fatalf("expected 1 event, got %d", len(deal.Events))
if len(dealResp.BotEvents) != 1 {
t.Fatalf("expected 1 event, got %d", len(dealResp.BotEvents))
}
}

Expand All @@ -92,36 +66,20 @@ func TestExampleEventAddition(t *testing.T) {
mockServer := server.NewTestServer(t)
defer mockServer.Close()

mockServer.AddBot(server.Bot{ID: 1, Name: "Test Bot", Enabled: true})
mockServer.AddDeal(1, server.Deal{
ID: 101,
BotID: 1,
Status: "active",
Events: []server.BotEvent{},
})
mockServer.AddBot(server.NewBot(1, "Test Bot", 12345, true))
deal := server.NewDeal(101, 1, "USDT_BTC", "active")
mockServer.AddDeal(deal)

// Simulate safety order being placed
mockServer.AddBotEvent(101, server.BotEvent{
CreatedAt: "2024-01-15T10:32:00.000Z",
Action: "place",
Coin: "BTC",
Type: "buy",
Status: "active",
Price: "48750.0",
Size: "0.0004",
OrderType: "safety",
OrderSize: 2,
OrderPosition: 1,
IsMarket: false,
})
mockServer.AddBotEventToDeal(101, "Placing safety order. Price: 48750.0 USDT Size: 0.0004 BTC")

// Fetch deal and verify event was added
deal, ok := mockServer.GetDealByID(101)
dealResp, ok := mockServer.GetDealByID(101)
if !ok {
t.Fatal("deal not found")
}
if len(deal.Events) != 1 {
t.Fatalf("expected 1 event, got %d", len(deal.Events))
if len(dealResp.BotEvents) != 1 {
t.Fatalf("expected 1 event, got %d", len(dealResp.BotEvents))
}
}

Expand Down Expand Up @@ -161,3 +119,53 @@ func TestExampleErrorSimulation(t *testing.T) {
t.Fatalf("expected status 200, got %d", resp.StatusCode)
}
}

// TestExampleVCRLoading demonstrates loading real API responses from VCR cassettes
func TestExampleVCRLoading(t *testing.T) {
mockServer := server.NewTestServer(t)
defer mockServer.Close()

// Load a VCR cassette with a real recorded deal from 3commas API
// Note: go-vcr appends .yaml automatically, so omit the extension
err := mockServer.LoadVCRCassette("../testdata/fixtures/deal_2376446537")
if err != nil {
t.Fatalf("failed to load VCR cassette: %v", err)
}

// Now the deal is available via the API with ALL real data
resp, err := http.Get(mockServer.URL() + "/ver1/deals/2376446537/show")
if err != nil {
t.Fatalf("failed to get deal: %v", err)
}
defer resp.Body.Close()

var deal tcmock.Deal
if err := json.NewDecoder(resp.Body).Decode(&deal); err != nil {
t.Fatalf("failed to decode deal: %v", err)
}

// Verify we got real data from the VCR recording
if deal.Pair != "USDT_DOGE" {
t.Fatalf("expected pair USDT_DOGE, got %s", deal.Pair)
}

// Most importantly: bot_events are preserved from the real API!
if len(deal.BotEvents) != 3 {
t.Fatalf("expected 3 bot events from VCR, got %d", len(deal.BotEvents))
}

// The messages are real ones from 3commas
if deal.BotEvents[0].Message == nil ||
*deal.BotEvents[0].Message != "Placing averaging order (9 out of 9). Price: market Size: 25.0008 USDT (110.0 DOGE)" {
t.Fatal("bot_events not preserved correctly from VCR")
}

// Bot was auto-created from the deal data
bot, ok := mockServer.GetBot(16511317)
if !ok {
t.Fatal("bot should have been auto-created from deal")
}
if bot.AccountName != "Demo Account 2080398" {
t.Fatalf("expected account name from VCR, got %s", bot.AccountName)
}
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen
require (
github.com/oapi-codegen/nullable v1.1.0
github.com/oapi-codegen/runtime v1.1.2
gopkg.in/dnaeon/go-vcr.v4 v4.0.5
)

require (
Expand All @@ -15,6 +16,7 @@ require (
github.com/getkin/kin-openapi v0.133.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/google/uuid v1.5.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
Expand Down Expand Up @@ -171,6 +173,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/dnaeon/go-vcr.v4 v4.0.5 h1:I0hpTIvD5rII+8LgYGrHMA2d4SQPoL6u7ZvJakWKsiA=
gopkg.in/dnaeon/go-vcr.v4 v4.0.5/go.mod h1:dRos81TkW9C1WJt6tTaE+uV2Lo8qJT3AG2b35+CB/nQ=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
Expand Down
24 changes: 12 additions & 12 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ type TestServer struct {
mu sync.RWMutex

// State
bots map[int]*Bot
deals map[int]*Deal
bots map[int]*tcmock.Bot
deals map[int]*tcmock.Deal

// Error simulation
rateLimitEnabled bool
Expand All @@ -30,8 +30,8 @@ type TestServer struct {
// NewTestServer creates a new mock 3Commas server for testing
func NewTestServer(t *testing.T) *TestServer {
ts := &TestServer{
bots: make(map[int]*Bot),
deals: make(map[int]*Deal),
bots: make(map[int]*tcmock.Bot),
deals: make(map[int]*tcmock.Deal),
botErrors: make(map[int]error),
dealErrors: make(map[int]error),
}
Expand All @@ -58,8 +58,8 @@ func (ts *TestServer) Reset() {
ts.mu.Lock()
defer ts.mu.Unlock()

ts.bots = make(map[int]*Bot)
ts.deals = make(map[int]*Deal)
ts.bots = make(map[int]*tcmock.Bot)
ts.deals = make(map[int]*tcmock.Deal)
ts.botErrors = make(map[int]error)
ts.dealErrors = make(map[int]error)
ts.rateLimitEnabled = false
Expand All @@ -85,14 +85,14 @@ func (ts *TestServer) ListBots(w http.ResponseWriter, r *http.Request, params tc
}

// Filter bots based on scope parameter
var result []Bot
var result []tcmock.Bot
for _, bot := range ts.bots {
// Apply scope filter if provided
if params.Scope != nil {
if *params.Scope == tcmock.Enabled && !bot.Enabled {
if *params.Scope == tcmock.Enabled && !bot.IsEnabled {
continue
}
if *params.Scope == tcmock.Disabled && bot.Enabled {
if *params.Scope == tcmock.Disabled && bot.IsEnabled {
continue
}
}
Expand All @@ -110,15 +110,15 @@ func (ts *TestServer) ListDeals(w http.ResponseWriter, r *http.Request, params t
defer ts.mu.RUnlock()

// Filter deals based on parameters
var result []Deal
var result []tcmock.Deal
for _, deal := range ts.deals {
// Apply bot_id filter if provided
if params.BotId != nil && deal.BotID != *params.BotId {
if params.BotId != nil && deal.BotId != *params.BotId {
continue
}

// Apply scope filter if provided
if params.Scope != nil && string(*params.Scope) != deal.Status {
if params.Scope != nil && tcmock.DealStatus(*params.Scope) != deal.Status {
continue
}

Expand Down
Loading