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
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

replace gopkg.in/dnaeon/go-vcr.v4 => github.com/dnaeon/go-vcr/v4 v4.0.5
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnaeon/go-vcr/v4 v4.0.5 h1:mHtf8dT8cvBT6n4YZ3zwOmqFGxHZppgitHUU9l5FZLo=
github.com/dnaeon/go-vcr/v4 v4.0.5/go.mod h1:dRos81TkW9C1WJt6tTaE+uV2Lo8qJT3AG2b35+CB/nQ=
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w=
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q=
Expand Down Expand Up @@ -173,8 +175,6 @@ 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
13 changes: 13 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ type TestServer struct {
bots map[int]*tcmock.Bot
deals map[int]*tcmock.Deal

// Configuration
allowDuplicateIDs bool

// Error simulation
rateLimitEnabled bool
rateLimitRetry int
Expand Down Expand Up @@ -63,6 +66,16 @@ func (ts *TestServer) Reset() {
ts.botErrors = make(map[int]error)
ts.dealErrors = make(map[int]error)
ts.rateLimitEnabled = false
ts.allowDuplicateIDs = false
}

// AllowDuplicateIDs enables or disables duplicate ID checking
// When enabled, loading VCR cassettes with duplicate IDs will not return an error
// Instead, duplicate entries will be skipped (existing entries are preserved)
func (ts *TestServer) AllowDuplicateIDs(allow bool) {
ts.mu.Lock()
defer ts.mu.Unlock()
ts.allowDuplicateIDs = allow
}

// ListBots implements the ServerInterface method for GET /ver1/bots
Expand Down
44 changes: 31 additions & 13 deletions server/vcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ var (
// LoadVCRCassette loads a VCR cassette and populates mock server state
// - Bots and Deals are loaded with ALL their data from real API responses
// - bot_events are PRESERVED exactly as recorded (this is the valuable part!)
// - Duplicate IDs will return an error
// - Duplicate IDs will return an error unless AllowDuplicateIDs(true) is set
// - Non-2xx responses are skipped
func (ts *TestServer) LoadVCRCassette(cassettePath string) error {
// Load cassette from file
Expand Down Expand Up @@ -94,12 +94,18 @@ func (ts *TestServer) loadDealFromJSON(jsonBody string) error {

// Check for duplicate
ts.mu.RLock()
if _, exists := ts.deals[deal.Id]; exists {
ts.mu.RUnlock()
return fmt.Errorf("duplicate deal ID %d found in VCR cassette", deal.Id)
}
_, exists := ts.deals[deal.Id]
allowDuplicates := ts.allowDuplicateIDs
ts.mu.RUnlock()

if exists {
if !allowDuplicates {
return fmt.Errorf("duplicate deal ID %d found in VCR cassette", deal.Id)
}
// Skip this deal if duplicates are allowed (preserve existing entry)
return nil
}

// Check if bot exists, create a minimal one if not
ts.mu.RLock()
botExists := ts.bots[deal.BotId] != nil
Expand Down Expand Up @@ -130,12 +136,18 @@ func (ts *TestServer) loadDealsListFromJSON(jsonBody string) error {
for _, deal := range deals {
// Check for duplicate
ts.mu.RLock()
if _, exists := ts.deals[deal.Id]; exists {
ts.mu.RUnlock()
return fmt.Errorf("duplicate deal ID %d found in VCR cassette", deal.Id)
}
_, exists := ts.deals[deal.Id]
allowDuplicates := ts.allowDuplicateIDs
ts.mu.RUnlock()

if exists {
if !allowDuplicates {
return fmt.Errorf("duplicate deal ID %d found in VCR cassette", deal.Id)
}
// Skip this deal if duplicates are allowed (preserve existing entry)
continue
}

// Check if bot exists, create a minimal one if not
ts.mu.RLock()
botExists := ts.bots[deal.BotId] != nil
Expand Down Expand Up @@ -168,12 +180,18 @@ func (ts *TestServer) loadBotsListFromJSON(jsonBody string) error {
for _, bot := range bots {
// Check for duplicate
ts.mu.RLock()
if _, exists := ts.bots[bot.Id]; exists {
ts.mu.RUnlock()
return fmt.Errorf("duplicate bot ID %d found in VCR cassette", bot.Id)
}
_, exists := ts.bots[bot.Id]
allowDuplicates := ts.allowDuplicateIDs
ts.mu.RUnlock()

if exists {
if !allowDuplicates {
return fmt.Errorf("duplicate bot ID %d found in VCR cassette", bot.Id)
}
// Skip this bot if duplicates are allowed (preserve existing entry)
continue
}

// Add bot with all its data
ts.mu.Lock()
botCopy := bot // Create a copy to get the correct pointer
Expand Down
38 changes: 38 additions & 0 deletions server/vcr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,44 @@ func TestLoadVCRCassette_DuplicateError(t *testing.T) {
}
}

func TestLoadVCRCassette_AllowDuplicates(t *testing.T) {
ts := NewTestServer(t)
defer ts.Close()

// Enable duplicate ID allowance
ts.AllowDuplicateIDs(true)

// Load cassette first time - should succeed
err := ts.LoadVCRCassette("../testdata/fixtures/deal_2376446537")
if err != nil {
t.Fatalf("first load failed: %v", err)
}

// Verify the deal was loaded
deal, ok := ts.GetDealByID(2376446537)
if !ok {
t.Fatal("deal 2376446537 not found after first load")
}
if deal.Pair != "USDT_DOGE" {
t.Errorf("expected pair USDT_DOGE, got %s", deal.Pair)
}

// Load same cassette again - should succeed (duplicates allowed)
err = ts.LoadVCRCassette("../testdata/fixtures/deal_2376446537")
if err != nil {
t.Fatalf("second load failed with AllowDuplicateIDs enabled: %v", err)
}

// Verify the original deal is still there and unchanged
deal, ok = ts.GetDealByID(2376446537)
if !ok {
t.Fatal("deal 2376446537 not found after second load")
}
if deal.Pair != "USDT_DOGE" {
t.Errorf("expected pair USDT_DOGE after second load, got %s", deal.Pair)
}
}

func TestLoadVCRCassettes_Multiple(t *testing.T) {
ts := NewTestServer(t)
defer ts.Close()
Expand Down