Skip to content
This repository has been archived by the owner on Oct 21, 2024. It is now read-only.

Commit

Permalink
refactor: timeout mock client added, documentation and better packaging
Browse files Browse the repository at this point in the history
  • Loading branch information
kuba-4chain committed Sep 17, 2023
1 parent bb4d464 commit 6d9d8bd
Show file tree
Hide file tree
Showing 6 changed files with 419 additions and 142 deletions.
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ custom features to work with multiple nodes and retry logic.

- [x] Possibility to use custom http client [WithHTTPClient](https://github.com/bitcoin-sv/go-broadcast-client/blob/main/broadcast/broadcast-client/client_builder.go#L19)

- [x] Mock Client for testing purposes [details](link)

## How to use it?

### Create client
Expand Down Expand Up @@ -302,3 +304,45 @@ type Transaction struct {
| 109 | `REJECTED` | The transaction has been rejected by the Bitcoin network.

*Source* [Arc API](https://github.com/bitcoin-sv/arc/blob/main/README.md)


## MockClientBuilder

Mock Client allows you to test your code without using an actual client and without connecting to any nodes.

### WithMockArc Method

This method allows you to create a client with a different Mock Type passed as parameter.

```go
client := broadcast_client_mock.Builder().
WithMockArc(broadcast_client_mock.MockSucces).
Build()
```

| MockType | Description
|---------------|-----------------------------------------------------------------------------------
| `MockSucces` | Client will return a successful response from all methods.
| `MockFailure` | Client will return an error that no miner returned a response from all methods.
| `MockTimeout` | Client will return a successful response after a timeout from all methods.

#### MockTimeout

MockTimeout will return a successfull response after around ~10ms more than the timeout provided in the context that is passed to it's method.
Example:
```go
client := broadcast_client_mock.Builder().
WithMockArc(broadcast_client_mock.MockTimeout).
Build()
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) // Creating a timeout context with 2 seconds timeout
defer cancel()
result, err := client.GetPolicyQuote(ctx) // client will return a response after around 2 seconds and 10 milliseconds, therefore exceeding the timeout
```
If you pass the context without a timeout, the client will instantly return a successful response (just like from a MockSuccess type).
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package broadcast_client
package broadcast_client_mock

import (
broadcast_api "github.com/bitcoin-sv/go-broadcast-client/broadcast"
"github.com/bitcoin-sv/go-broadcast-client/broadcast/internal/arc/mocks"
"github.com/bitcoin-sv/go-broadcast-client/broadcast/internal/composite"
"github.com/bitcoin-sv/go-broadcast-client/httpclient"
)

// MockType is an enum that is used as parameter to WithMockArc
Expand All @@ -15,22 +17,31 @@ const (
MockTimeout
)

type builder struct {
factories []composite.BroadcastFactory
client httpclient.HTTPInterface
}

// Builder is used to prepare the mock broadcast client. It is recommended
// to use that builder for creating the mock broadcast client.
func Builder() *builder {
return &builder{}
}

// WithMockArc creates a mock client for testing purposes. It takes mock type as argument
// and creates a mock client that satisfies the client interface with methods that return
// success or specific error based on this mock type argument.
func (cb *builder) WithMockArc(config ArcClientConfig, mockType MockType) *builder {
// success or specific error based on this mock type argument. It allows for creating
// multiple mock clients.
func (cb *builder) WithMockArc(mockType MockType) *builder {
var clientToReturn broadcast_api.Client

switch mockType {
case MockSuccess:
clientToReturn = mocks.NewArcClientMock()
break
case MockFailure:
clientToReturn = mocks.NewArcClientMockFailure()
break
case MockTimeout:
clientToReturn = mocks.NewArcClientMockTimeout()
break
default:
clientToReturn = mocks.NewArcClientMock()
}
Expand All @@ -40,3 +51,11 @@ func (cb *builder) WithMockArc(config ArcClientConfig, mockType MockType) *build
})
return cb
}

// Build builds the broadcast client based on the provided configuration.
func (cb *builder) Build() broadcast_api.Client {
if len(cb.factories) == 1 {
return cb.factories[0]()
}
return composite.NewBroadcasterWithDefaultStrategy(cb.factories...)
}
254 changes: 254 additions & 0 deletions broadcast/broadcast-client-mock/mock_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
package broadcast_client_mock

import (
"context"
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/bitcoin-sv/go-broadcast-client/broadcast"
)

func TestMockClientSuccess(t *testing.T) {
t.Run("Should successfully query for Policy Quote from mock Arc Client with Success Mock Type", func(t *testing.T) {
// given
broadcaster := Builder().
WithMockArc(MockSuccess).
Build()

// when
result, err := broadcaster.GetPolicyQuote(context.Background())

// then
assert.NoError(t, err)
assert.NotNil(t, result)
})

t.Run("Should successfully query for Fee Quote from mock Arc Client with Success Mock Type", func(t *testing.T) {
// given
broadcaster := Builder().
WithMockArc(MockSuccess).
Build()

// when
result, err := broadcaster.GetFeeQuote(context.Background())

// then
assert.NoError(t, err)
assert.NotNil(t, result)
})

t.Run("Should successfully query for transaction from mock Arc Client with Success Mock Type", func(t *testing.T) {
// given
broadcaster := Builder().
WithMockArc(MockSuccess).
Build()

// when
result, err := broadcaster.QueryTransaction(context.Background(), "test-txid")

// then
assert.NoError(t, err)
assert.NotNil(t, result)
})

t.Run("Should return successful submit transaction response from mock Arc Client with Success Mock Type", func(t *testing.T) {
// given
broadcaster := Builder().
WithMockArc(MockSuccess).
Build()

// when
result, err := broadcaster.SubmitTransaction(context.Background(), &broadcast.Transaction{RawTx: "test-rawtx"})

// then
assert.NoError(t, err)
assert.NotNil(t, result)
})

t.Run("Should return successful submit batch transactions response from mock Arc Client with Success Mock Type", func(t *testing.T) {
// given
broadcaster := Builder().
WithMockArc(MockSuccess).
Build()

// when
result, err := broadcaster.SubmitBatchTransactions(context.Background(), []*broadcast.Transaction{{RawTx: "test-rawtx"}, {RawTx: "test2-rawtx"}})

// then
assert.NoError(t, err)
assert.NotNil(t, result)
})
}

func TestMockClientFailure(t *testing.T) {
t.Run("Should return error from GetPolicyQuote method of mock Arc Client with Failure Mock Type", func(t *testing.T) {
// given
broadcaster := Builder().
WithMockArc(MockFailure).
Build()

// when
result, err := broadcaster.GetPolicyQuote(context.Background())

// then
assert.Error(t, err)
assert.Nil(t, result)
assert.EqualError(t, err, broadcast.ErrNoMinerResponse.Error())
})

t.Run("Should return error from GetFeeQuote method of mock Arc Client with Failure Mock Type", func(t *testing.T) {
// given
broadcaster := Builder().
WithMockArc(MockFailure).
Build()

// when
result, err := broadcaster.GetFeeQuote(context.Background())

// then
assert.Error(t, err)
assert.Nil(t, result)
assert.EqualError(t, err, broadcast.ErrNoMinerResponse.Error())
})

t.Run("Should return error from QueryTransaction method of mock Arc Client with Failure Mock Type", func(t *testing.T) {
// given
broadcaster := Builder().
WithMockArc(MockFailure).
Build()

// when
result, err := broadcaster.QueryTransaction(context.Background(), "test-txid")

// then
assert.Error(t, err)
assert.Nil(t, result)
assert.EqualError(t, err, broadcast.ErrAllBroadcastersFailed.Error())
})

t.Run("Should return error from SubmitTransaction method of mock Arc Client with Failure Mock Type", func(t *testing.T) {
// given
broadcaster := Builder().
WithMockArc(MockFailure).
Build()

// when
result, err := broadcaster.SubmitTransaction(context.Background(), &broadcast.Transaction{RawTx: "test-rawtx"})

// then
assert.Error(t, err)
assert.Nil(t, result)
assert.EqualError(t, err, broadcast.ErrAllBroadcastersFailed.Error())
})

t.Run("Should return error from SubmitBatchTransaction method of mock Arc Client with Failure Mock Type", func(t *testing.T) {
// given
broadcaster := Builder().
WithMockArc(MockFailure).
Build()

// when
result, err := broadcaster.SubmitBatchTransactions(context.Background(), []*broadcast.Transaction{{RawTx: "test-rawtx"}, {RawTx: "test2-rawtx"}})

// then
assert.Error(t, err)
assert.Nil(t, result)
assert.EqualError(t, err, broadcast.ErrAllBroadcastersFailed.Error())
})
}

func TestMockClientTimeout(t *testing.T) {
const defaultTestTime = 200*time.Millisecond

t.Run("Should successfully query for Policy Quote after a timeout period from mock Arc Client with Timeout Mock Type", func(t *testing.T) {
// given
broadcaster := Builder().
WithMockArc(MockTimeout).
Build()
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTime)
defer cancel()
startTime := time.Now()

// when
result, err := broadcaster.GetPolicyQuote(ctx)

// then
assert.NoError(t, err)
assert.NotNil(t, result)
assert.Greater(t, time.Now().Sub(startTime), defaultTestTime)
})

t.Run("Should successfully query for Fee Quote after a timeout period from mock Arc Client with Timeout Mock Type", func(t *testing.T) {
// given
broadcaster := Builder().
WithMockArc(MockTimeout).
Build()
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTime)
defer cancel()
startTime := time.Now()

// when
result, err := broadcaster.GetFeeQuote(ctx)

// then
assert.NoError(t, err)
assert.NotNil(t, result)
assert.Greater(t, time.Now().Sub(startTime), defaultTestTime)
})

t.Run("Should successfully query for transaction after a timeout period from mock Arc Client with Timeout Mock Type", func(t *testing.T) {
// given
broadcaster := Builder().
WithMockArc(MockTimeout).
Build()
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTime)
defer cancel()
startTime := time.Now()

// when
result, err := broadcaster.QueryTransaction(ctx, "test-txid")

// then
assert.NoError(t, err)
assert.NotNil(t, result)
assert.Greater(t, time.Now().Sub(startTime), defaultTestTime)
})

t.Run("Should return successful submit transaction response after a timeout period from mock Arc Client with Timeout Mock Type", func(t *testing.T) {
// given
broadcaster := Builder().
WithMockArc(MockTimeout).
Build()
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTime)
defer cancel()
startTime := time.Now()

// when
result, err := broadcaster.SubmitTransaction(ctx, &broadcast.Transaction{RawTx: "test-rawtx"})

// then
assert.NoError(t, err)
assert.NotNil(t, result)
assert.Greater(t, time.Now().Sub(startTime), defaultTestTime)
})

t.Run("Should return successful submit batch transactions response after a timeout period from mock Arc Client with Timeout Mock Type", func(t *testing.T) {
// given
broadcaster := Builder().
WithMockArc(MockTimeout).
Build()
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTime)
defer cancel()
startTime := time.Now()

// when
result, err := broadcaster.SubmitBatchTransactions(ctx, []*broadcast.Transaction{{RawTx: "test-rawtx"}, {RawTx: "test2-rawtx"}})

// then
assert.NoError(t, err)
assert.NotNil(t, result)
assert.Greater(t, time.Now().Sub(startTime), defaultTestTime)
})
}
Loading

0 comments on commit 6d9d8bd

Please sign in to comment.