diff --git a/README.md b/README.md index 0180802..bef4b0c 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,12 @@ to those versions. are performing active development against `sentry-go`. - [**sentry-go.v1**](https://gopkg.in/SierraSoftworks/sentry-go.v1) - `import ("gopkg.in/SierraSoftworks/sentry-go.v1")` + This version of `sentry-go` maintains API compatibility with the package's v1 API. If you used + `sentry-go` for a project prior to 2019-09-25 then this is the version you should retain until + you can update your code. It will receive bug and security fixes. + + - [**sentry-go.v2**](https://gopkg.in/SierraSoftworks/sentry-go.v2) - `import ("gopkg.in/SierraSoftworks/sentry-go.v2")` + This version is the most recent release of `sentry-go` and will maintain API compatibility. If you are creating a project that relies on `sentry-go` then this is the version you should use. @@ -46,7 +52,7 @@ package main import ( "fmt" - "gopkg.in/SierraSoftworks/sentry-go.v1" + "gopkg.in/SierraSoftworks/sentry-go.v2" "github.com/pkg/errors" ) @@ -78,7 +84,7 @@ import ( "net/http" "os" - "gopkg.in/SierraSoftworks/sentry-go.v1" + "gopkg.in/SierraSoftworks/sentry-go.v2" ) func main() { @@ -130,7 +136,7 @@ between different clients to impose custom behaviour for different portions of your application. ```go -import "gopkg.in/SierraSoftworks/sentry-go.v1" +import "gopkg.in/SierraSoftworks/sentry-go.v2" func main() { // Configure a new global send queue diff --git a/breadcrumb_test.go b/breadcrumb_test.go index aa314fc..92e77cc 100644 --- a/breadcrumb_test.go +++ b/breadcrumb_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleBreadcrumb() { @@ -32,104 +32,59 @@ func ExampleBreadcrumb() { } func TestBreadcrumb(t *testing.T) { - Convey("Breadcrumb", t, func() { - data := map[string]interface{}{ - "test": true, + data := map[string]interface{}{ + "test": true, + } + + t.Run("newBreadcrumb", func(t *testing.T) { + b := newBreadcrumb("default", data) + + if assert.NotNil(t, b) { + assert.Implements(t, (*Breadcrumb)(nil), b) + assert.Equal(t, "", b.Type, "It should set the correct type") + assert.NotEqual(t, 0, b.Timestamp, "It should set the timestamp") + assert.Equal(t, data, b.Data, "It should set the correct data") } + }) + + t.Run("WithMessage()", func(t *testing.T) { + b := newBreadcrumb("default", data) + + if assert.NotNil(t, b) { + bb := b.WithMessage("test") + assert.Equal(t, b, bb, "It should return the breadcrumb for chaining") + assert.Equal(t, "test", b.Message) + } + }) + + t.Run("WithCategory()", func(t *testing.T) { + b := newBreadcrumb("default", data) - Convey("newBreadcrumb", func() { - - Convey("Should return a Breadcrumb type", func() { - b := newBreadcrumb("default", data) - So(b, ShouldNotBeNil) - - So(b, ShouldImplement, (*Breadcrumb)(nil)) - }) - - Convey("Should set the timestamp", func() { - b := newBreadcrumb("default", data) - So(b, ShouldNotBeNil) - So(b.Timestamp, ShouldNotEqual, 0) - }) - - Convey("Should set the data", func() { - b := newBreadcrumb("default", data) - So(b, ShouldNotBeNil) - So(b.Data, ShouldEqual, data) - }) - - Convey("Should set the Type correctly", func() { - Convey("With default type", func() { - b := newBreadcrumb("default", data) - - So(b, ShouldNotBeNil) - So(b.Type, ShouldEqual, "") - }) - - Convey("With non-default type", func() { - b := newBreadcrumb("test", data) - - So(b, ShouldNotBeNil) - So(b.Type, ShouldEqual, "test") - }) - }) - }) - - Convey("WithMessage()", func() { - b := newBreadcrumb("default", data) - So(b, ShouldNotBeNil) - - Convey("Should update the Message field", func() { - b.WithMessage("test") - So(b.Message, ShouldEqual, "test") - }) - - Convey("Should be chainable", func() { - So(b.WithMessage("test"), ShouldEqual, b) - }) - }) - - Convey("WithCategory()", func() { - b := newBreadcrumb("default", data) - So(b, ShouldNotBeNil) - - Convey("Should update the Category field", func() { - b.WithCategory("test") - So(b.Category, ShouldEqual, "test") - }) - - Convey("Should be chainable", func() { - So(b.WithCategory("test"), ShouldEqual, b) - }) - }) - - Convey("WithLevel()", func() { - b := newBreadcrumb("default", data) - So(b, ShouldNotBeNil) - - Convey("Should update the Level field", func() { - b.WithLevel(Error) - So(b.Level, ShouldEqual, Error) - }) - - Convey("Should be chainable", func() { - So(b.WithLevel(Error), ShouldEqual, b) - }) - }) - - Convey("WithTimestamp()", func() { - b := newBreadcrumb("default", data) - So(b, ShouldNotBeNil) - - Convey("Should update the Timestamp field", func() { - now := time.Now() - b.WithTimestamp(now) - So(b.Timestamp, ShouldEqual, now.UTC().Unix()) - }) - - Convey("Should be chainable", func() { - So(b.WithTimestamp(time.Now()), ShouldEqual, b) - }) - }) + if assert.NotNil(t, b) { + bb := b.WithCategory("test") + assert.Equal(t, b, bb, "It should return the breadcrumb for chaining") + assert.Equal(t, "test", b.Category) + } + }) + + t.Run("WithLevel()", func(t *testing.T) { + b := newBreadcrumb("default", data) + + if assert.NotNil(t, b) { + bb := b.WithLevel(Error) + assert.Equal(t, b, bb, "It should return the breadcrumb for chaining") + assert.Equal(t, Error, b.Level) + } + }) + + t.Run("WithTimestamp()", func(t *testing.T) { + b := newBreadcrumb("default", data) + now := time.Now() + + if assert.NotNil(t, b) { + bb := b.WithTimestamp(now) + assert.Equal(t, b, bb, "It should return the breadcrumb for chaining") + assert.Equal(t, now.UTC().Unix(), b.Timestamp) + } }) } diff --git a/breadcrumbs_test.go b/breadcrumbs_test.go index c453397..b9893f1 100644 --- a/breadcrumbs_test.go +++ b/breadcrumbs_test.go @@ -3,7 +3,7 @@ package sentry import ( "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleDefaultBreadcrumbs() { @@ -55,234 +55,173 @@ func ExampleBreadcrumbs() { } func TestBreadcrumbs(t *testing.T) { - Convey("Breadcrumbs", t, func() { - Convey("Should be registered as a default options provider", func() { - provider := testGetOptionsProvider(&breadcrumbsList{}) - So(provider, ShouldNotBeNil) - }) - - Convey("Should expose a DefaultBreadcrumbs() collection", func() { - So(DefaultBreadcrumbs(), ShouldNotBeNil) - So(DefaultBreadcrumbs(), ShouldImplement, (*BreadcrumbsList)(nil)) - }) - - Convey("Breadcrumbs()", func() { - Convey("Should use the correct Class()", func() { - l := NewBreadcrumbsList(3) - So(l, ShouldNotBeNil) - - So(Breadcrumbs(l).Class(), ShouldEqual, "breadcrumbs") - }) + t.Run("Options Providers", func(t *testing.T) { + assert.NotNil(t, testGetOptionsProvider(t, &breadcrumbsList{}), "Breadcrumbs should be registered as a default options provider") + }) - Convey("Should return nil if the list is nil", func() { - So(Breadcrumbs(nil), ShouldBeNil) - }) - }) + t.Run("DefaultBreadcrumbs()", func(t *testing.T) { + assert.NotNil(t, DefaultBreadcrumbs()) + assert.Implements(t, (*BreadcrumbsList)(nil), DefaultBreadcrumbs()) + }) - Convey("NewBreadcrumbsList()", func() { - l := NewBreadcrumbsList(3) - So(l, ShouldNotBeNil) + t.Run("Breadcrumbs()", func(t *testing.T) { + assert.Nil(t, Breadcrumbs(nil), "it should return a nil option if it receives a nil breadcrumbs list") - ll, ok := l.(*breadcrumbsList) - So(ok, ShouldBeTrue) + l := NewBreadcrumbsList(3) + assert.NotNil(t, l, "it should create a breadcrumbs list") - Convey("Should implement Option interface", func() { - So(l, ShouldImplement, (*Option)(nil)) - }) + b := Breadcrumbs(l) + assert.NotNil(t, b, "it should return an option when the list is not nil") - Convey("Should initialize correctly", func() { - So(ll.MaxLength, ShouldEqual, 3) - So(ll.Length, ShouldEqual, 0) - So(ll.Head, ShouldBeNil) - So(ll.Tail, ShouldBeNil) - }) + assert.Equal(t, "breadcrumbs", b.Class(), "it should use the correct option class") + }) +} - Convey("NewDefault()", func() { - data := map[string]interface{}{ - "test": true, - } - - Convey("Should return a Breadcrumb", func() { - b := l.NewDefault(data) - So(b, ShouldImplement, (*Breadcrumb)(nil)) - }) - - Convey("Should use a default breadcrumb type", func() { - b := l.NewDefault(data) - bb, ok := b.(*breadcrumb) - So(ok, ShouldBeTrue) - So(bb.Type, ShouldEqual, "") - So(bb.Data, ShouldEqual, data) - }) - - Convey("Should allow you to specify nil for default data", func() { - b := l.NewDefault(nil) - bb, ok := b.(*breadcrumb) - So(ok, ShouldBeTrue) - So(bb.Data, ShouldResemble, map[string]interface{}{}) - }) - - Convey("Should insert the breadcrumb into the list at its tail", func() { - b := l.NewDefault(data) - So(ll.Length, ShouldEqual, 1) - So(ll.Tail, ShouldNotBeNil) - So(ll.Tail.Value, ShouldEqual, b) - }) - }) +func TestNewBreadcrumbsList(t *testing.T) { + l := NewBreadcrumbsList(3) + assert.NotNil(t, l, "it should return a non-nil list") - Convey("NewNavigation()", func() { - Convey("Should return a Breadcrumb", func() { - b := l.NewNavigation("/from", "/to") - So(b, ShouldImplement, (*Breadcrumb)(nil)) - }) - - Convey("Should use a navigation breadcrumb type", func() { - b := l.NewNavigation("/from", "/to") - bb, ok := b.(*breadcrumb) - So(ok, ShouldBeTrue) - So(bb.Type, ShouldEqual, "navigation") - So(bb.Data, ShouldResemble, map[string]interface{}{ - "from": "/from", - "to": "/to", - }) - }) - - Convey("Should insert the breadcrumb into the list at its tail", func() { - b := l.NewNavigation("/from", "/to") - So(ll.Length, ShouldEqual, 1) - So(ll.Tail, ShouldNotBeNil) - So(ll.Tail.Value, ShouldEqual, b) - }) - }) + assert.Implements(t, (*Option)(nil), l, "it should implement the option interface") - Convey("NewHTTPRequest()", func() { - Convey("Should return a Breadcrumb", func() { - b := l.NewHTTPRequest("GET", "/test", 200, "OK") - So(b, ShouldImplement, (*Breadcrumb)(nil)) - }) - - Convey("Should use a navigation breadcrumb type", func() { - b := l.NewHTTPRequest("GET", "/test", 200, "OK") - bb, ok := b.(*breadcrumb) - So(ok, ShouldBeTrue) - So(bb.Type, ShouldEqual, "http") - So(bb.Data, ShouldResemble, map[string]interface{}{ - "method": "GET", - "url": "/test", - "status_code": 200, - "reason": "OK", - }) - }) - - Convey("Should insert the breadcrumb into the list at its tail", func() { - b := l.NewHTTPRequest("GET", "/test", 200, "OK") - So(ll.Length, ShouldEqual, 1) - So(ll.Tail, ShouldNotBeNil) - So(ll.Tail.Value, ShouldEqual, b) - }) - }) + ll, ok := l.(*breadcrumbsList) + assert.True(t, ok, "it should actually be a *breadcrumbsList") - Convey("WithSize()", func() { - Convey("Should be chainable", func() { - So(l.WithSize(5), ShouldEqual, l) - }) - - Convey("Should update the max length field", func() { - So(ll.MaxLength, ShouldEqual, 3) - l.WithSize(5) - So(ll.MaxLength, ShouldEqual, 5) - }) - - Convey("Should remove elements which push the length over the limit", func() { - var b Breadcrumb - for i := 0; i < 3; i++ { - b = l.NewDefault(map[string]interface{}{ - "index": i, - }) - So(b, ShouldNotBeNil) - } - - So(ll.Length, ShouldEqual, 3) - l.WithSize(1) - So(ll.Length, ShouldEqual, 1) - So(ll.Head, ShouldNotBeNil) - So(ll.Head.Next, ShouldBeNil) - So(ll.Head.Value, ShouldEqual, b) - - So(b, ShouldHaveSameTypeAs, &breadcrumb{}) - So(b.(*breadcrumb).Data, ShouldResemble, map[string]interface{}{ - "index": 2, - }) - }) - - Convey("Should empty the list if the size is set to 0", func() { - var b Breadcrumb - for i := 0; i < 3; i++ { - b = l.NewDefault(map[string]interface{}{ - "index": i, - }) - So(b, ShouldNotBeNil) - } - - l.WithSize(0).WithSize(3) - So(ll.Length, ShouldEqual, 0) - So(ll.Head, ShouldBeNil) - So(ll.Tail, ShouldBeNil) - }) - }) + assert.Equal(t, 3, ll.MaxLength, "it should have the right max length") + assert.Equal(t, 0, ll.Length, "it should start with no breadcrumbs") + assert.Nil(t, ll.Head, "it should have no head to start with") + assert.Nil(t, ll.Tail, "it should have no tail to start with") + + t.Run("NewDefault(nil)", func(t *testing.T) { + b := l.NewDefault(nil) + assert.NotNil(t, b, "it should return a non-nil breadcrumb") + assert.Implements(t, (*Breadcrumb)(nil), b, "the breadcrumb should implement the Breadcrumb interface") + + assert.NotNil(t, ll.Tail, "the list's tail should no longer be nil") + assert.Equal(t, ll.Tail.Value, b, "the list's tail should now be the new breadcrumb") + + bb, ok := b.(*breadcrumb) + assert.True(t, ok, "it should actually be a *breadcrumb object") + assert.Equal(t, "", bb.Type, "it should use the default breadcrumb type") + assert.Equal(t, map[string]interface{}{}, bb.Data, "it should use the passed breadcrumb data") + }) + + t.Run("NewDefault(data)", func(t *testing.T) { + data := map[string]interface{}{ + "test": true, + } + + b := l.NewDefault(data) + assert.NotNil(t, b, "it should return a non-nil breadcrumb") + assert.Implements(t, (*Breadcrumb)(nil), b, "the breadcrumb should implement the Breadcrumb interface") - Convey("append()", func() { - Convey("Should evict older entries when we run over the max length", func() { - var b Breadcrumb - for i := 0; i < 10; i++ { - b = l.NewDefault(map[string]interface{}{ - "index": i, - }) - So(b, ShouldNotBeNil) - } - - So(ll.Length, ShouldEqual, 3) - - So(b, ShouldHaveSameTypeAs, &breadcrumb{}) - So(b.(*breadcrumb).Data, ShouldResemble, map[string]interface{}{ - "index": 9, - }) - }) + assert.NotNil(t, ll.Tail, "the list's tail should no longer be nil") + assert.Equal(t, ll.Tail.Value, b, "the list's tail should now be the new breadcrumb") + + bb, ok := b.(*breadcrumb) + assert.True(t, ok, "it should actually be a *breadcrumb object") + assert.Equal(t, "", bb.Type, "it should use the default breadcrumb type") + assert.Equal(t, data, bb.Data, "it should use the passed breadcrumb data") + }) + + t.Run("NewNavigation()", func(t *testing.T) { + b := l.NewNavigation("/from", "/to") + assert.NotNil(t, b, "it should return a non-nil breadcrumb") + assert.Implements(t, (*Breadcrumb)(nil), b, "the breadcrumb should implement the Breadcrumb interface") + + assert.NotNil(t, ll.Tail, "the list's tail should no longer be nil") + assert.Equal(t, ll.Tail.Value, b, "the list's tail should now be the new breadcrumb") + + bb, ok := b.(*breadcrumb) + assert.True(t, ok, "it should actually be a *breadcrumb object") + assert.Equal(t, "navigation", bb.Type, "it should use the default breadcrumb type") + assert.Equal(t, map[string]interface{}{ + "from": "/from", + "to": "/to", + }, bb.Data, "it should use the correct breadcrumb data") + }) + + t.Run("NewHTTPRequest()", func(t *testing.T) { + b := l.NewHTTPRequest("GET", "/test", 200, "OK") + assert.NotNil(t, b, "it should return a non-nil breadcrumb") + assert.Implements(t, (*Breadcrumb)(nil), b, "the breadcrumb should implement the Breadcrumb interface") + + assert.NotNil(t, ll.Tail, "the list's tail should no longer be nil") + assert.Equal(t, ll.Tail.Value, b, "the list's tail should now be the new breadcrumb") + + bb, ok := b.(*breadcrumb) + assert.True(t, ok, "it should actually be a *breadcrumb object") + assert.Equal(t, "http", bb.Type, "it should use the default breadcrumb type") + assert.Equal(t, map[string]interface{}{ + "method": "GET", + "url": "/test", + "status_code": 200, + "reason": "OK", + }, bb.Data, "it should use the correct breadcrumb data") + }) + + t.Run("WithSize()", func(t *testing.T) { + cl := l.WithSize(5) + assert.Equal(t, l, cl, "it should return the list so that the call is chainable") + assert.Equal(t, 5, ll.MaxLength, "it should update the lists's max size") + + var b Breadcrumb + for i := 0; i < ll.MaxLength*2; i++ { + b = l.NewDefault(map[string]interface{}{ + "index": i, }) + } + + assert.Equal(t, ll.MaxLength, ll.Length, "the list should cap out at its max size") + + l.WithSize(1) + assert.Equal(t, 1, ll.Length, "the list should be resized to the new max size") + + assert.Equal(t, b, ll.Head.Value, "the head of the list should be the last breadcrumb which was added") + assert.Equal(t, b, ll.Tail.Value, "the tail of the list should be the last breadcrumb which was added") + }) - Convey("list()", func() { - Convey("Should handle an empty list correctly", func() { - ol := ll.list() - So(ol, ShouldNotBeNil) - So(ol, ShouldHaveLength, 0) - }) - - Convey("Should expose a non-empty list correctly", func() { - for i := 0; i < 3; i++ { - So(l.NewDefault(map[string]interface{}{ - "index": i, - }), ShouldNotBeNil) - } - - ol := ll.list() - So(ol, ShouldNotBeNil) - So(ol, ShouldHaveLength, 3) - for i, item := range ol { - So(item, ShouldHaveSameTypeAs, &breadcrumb{}) - So(item.(*breadcrumb).Data, ShouldContainKey, "index") - So(item.(*breadcrumb).Data["index"], ShouldEqual, i) - } - }) + t.Run("append()", func(t *testing.T) { + l.WithSize(0).WithSize(3) + + var b Breadcrumb + for i := 0; i < 10; i++ { + b = l.NewDefault(map[string]interface{}{ + "index": i, }) - }) + } + + assert.Equal(t, 3, ll.Length, "it should evict values to ensure that the length remains capped") + assert.Equal(t, b, ll.Tail.Value, "it should add new breadcrumbs at the end of the list") + }) + + t.Run("list()", func(t *testing.T) { + l.WithSize(0).WithSize(3) + assert.Equal(t, 0, ll.Length, "should start with an empty breadcrumbs list") + + ol := ll.list() + assert.NotNil(t, ol, "should not return nil if the list is empty") + assert.Len(t, ol, 0, "it should return an empty list") + + for i := 0; i < 10; i++ { + l.NewDefault(map[string]interface{}{"index": i}) + } + assert.Equal(t, 3, ll.Length, "should now have three breadcrumbs in the list") + + ol = ll.list() + assert.NotNil(t, ol, "should not return nil if the list is non-empty") + assert.Len(t, ol, 3, "should return the maximum number of items if the list is full") + + for i, item := range ol { + assert.IsType(t, &breadcrumb{}, item, "every list item should be a *breadcrumb") + assert.Equal(t, i+7, item.(*breadcrumb).Data["index"], "the items should be in the right order") + } + }) - Convey("MarshalJSON", func() { - l := NewBreadcrumbsList(5) - l.NewDefault(map[string]interface{}{"test": true}) + t.Run("MarshalJSON()", func(t *testing.T) { + l.WithSize(0).WithSize(5).NewDefault(map[string]interface{}{"test": true}) - data := testOptionsSerialize(Breadcrumbs(l)) - So(data, ShouldNotBeNil) - So(data, ShouldHaveSameTypeAs, []interface{}{}) - }) + data := testOptionsSerialize(t, Breadcrumbs(l)) + assert.NotNil(t, data, "should not return a nil result") + assert.IsType(t, []interface{}{}, data, "should return a JSON array") }) } diff --git a/client_test.go b/client_test.go index 806467c..60cee9b 100644 --- a/client_test.go +++ b/client_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleClient() { @@ -38,428 +38,303 @@ func ExampleDefaultClient() { ) } -func TestClient(t *testing.T) { - Convey("Client", t, func() { - Convey("DefaultClient()", func() { - So(DefaultClient(), ShouldNotBeNil) - So(DefaultClient(), ShouldImplement, (*Client)(nil)) - So(DefaultClient(), ShouldEqual, defaultClient) - }) - - Convey("NewClient()", func() { - Convey("Should return a Client", func() { - So(NewClient(), ShouldImplement, (*Client)(nil)) - }) - - Convey("Should set the client's parent to nil", func() { - cl := NewClient() - So(cl, ShouldNotBeNil) +func TestDefaultClient(t *testing.T) { + cl := DefaultClient() + assert.NotNil(t, cl, "it should not return nil") + assert.Implements(t, (*Client)(nil), cl, "it should implement the Client interface") + assert.Equal(t, defaultClient, cl, "it should return the global defaultClient") +} - cll, ok := cl.(*client) - So(ok, ShouldBeTrue) - So(cll.parent, ShouldBeNil) - }) +func TestNewClient(t *testing.T) { + opt := &testOption{} + cl := NewClient(opt) - Convey("Should set the client's options", func() { - opt := &testOption{} + if assert.NotNil(t, cl, "it should not return nil") { + assert.Implements(t, (*Client)(nil), cl, "it should implement the Client interface") - cl := NewClient(opt) - So(cl, ShouldNotBeNil) + cll, ok := cl.(*client) + assert.True(t, ok, "it should actually return a *client") - cll, ok := cl.(*client) - So(ok, ShouldBeTrue) - So(cll.options, ShouldResemble, []Option{opt}) - }) - }) + assert.Nil(t, cll.parent, "it should set the parent of the client to nil") + assert.Equal(t, []Option{opt}, cll.options, "it should set the client's options correctly") - Convey("Capture()", func() { + t.Run("Capture()", func(t *testing.T) { tr := testNewTestTransport() - So(tr, ShouldNotBeNil) + assert.NotNil(t, tr, "the test transport should not be nil") cl := NewClient(UseTransport(tr)) - So(cl, ShouldNotBeNil) + assert.NotNil(t, cl, "the client should not be nil") e := cl.Capture(Message("test")) - So(e, ShouldNotBeNil) + assert.NotNil(t, e, "the event handle should not be nil") ei, ok := e.(QueuedEventInternal) - So(ok, ShouldBeTrue) + assert.True(t, ok, "the event handle should be convertible to an internal queued event") select { case p := <-tr.ch: - So(p, ShouldEqual, ei.Packet()) - + assert.Equal(t, ei.Packet(), p, "the packet should match the internal event's packet") + pi, ok := p.(*packet) - So(ok, ShouldBeTrue) - So((*pi)[Message("test").Class()], ShouldResemble, Message("test")) + assert.True(t, ok, "the packet should actually be a *packet") + assert.Contains(t, *pi, Message("test").Class(), "the packet should contain the message") + assert.Equal(t, Message("test"), (*pi)[Message("test").Class()], "the message should be serialized under its key") case <-time.After(100 * time.Millisecond): - So(fmt.Errorf("timeout"), ShouldBeNil) + t.Error("the event was not dispatched within the timeout of 100ms") } }) - Convey("With()", func() { - Convey("Should return a new Client", func() { - cl := NewClient() - So(cl, ShouldNotBeNil) - - ctxCl := cl.With() - So(ctxCl, ShouldNotBeNil) - So(ctxCl, ShouldNotEqual, cl) - So(ctxCl, ShouldImplement, (*Client)(nil)) - }) - - Convey("Should set the client's parent", func() { - cl := NewClient() - So(cl, ShouldNotBeNil) - - ctxCl := cl.With() - So(ctxCl, ShouldNotBeNil) - - cll, ok := ctxCl.(*client) - So(ok, ShouldBeTrue) - So(cll.parent, ShouldEqual, cl) - }) - - Convey("Should set the client's options", func() { - cl := NewClient() - So(cl, ShouldNotBeNil) - - opt := &testOption{} - ctxCl := cl.With(opt) - So(ctxCl, ShouldNotBeNil) - - cll, ok := ctxCl.(*client) - So(ok, ShouldBeTrue) - So(cll.options, ShouldResemble, []Option{opt}) - }) - }) + t.Run("With()", func(t *testing.T) { + opt := &testOption{} + + ctxCl := cl.With(opt) + assert.NotNil(t, ctxCl, "the new client should not be nil") + assert.NotEqual(t, cl, ctxCl, "the new client should not be the same as the old client") - Convey("GetOption()", func() { - Convey("Should return nil for an unrecognized option", func() { - cl := NewClient() - So(cl.GetOption("unknown-option-class"), ShouldBeNil) - }) - - Convey("Should skip over nil options", func() { - cl := NewClient(nil) - So(cl.GetOption("unknown-option-class"), ShouldBeNil) - }) - - Convey("Should return the option if it is present", func() { - opt := &testOption{} - cl := NewClient(opt) - So(cl.GetOption("test"), ShouldEqual, opt) - }) - - Convey("Should return the most recent non-mergeable option", func() { - opt := &testOption{} - cl := NewClient(&testOption{}, opt) - So(cl.GetOption("test"), ShouldEqual, opt) - }) - - Convey("Should merge options when supported", func() { - cl := NewClient(&testMergeableOption{1}, &testMergeableOption{2}) - So(cl.GetOption("test"), ShouldResemble, &testMergeableOption{3}) - }) + cll, ok := ctxCl.(*client) + assert.True(t, ok, "the new client should actually be a *client") + assert.Equal(t, cl, cll.parent, "the new client should have its parent configured to be the original client") + assert.Equal(t, []Option{opt}, cll.options, "the new client should have the right list of options") }) - Convey("fullDefaultOptions()", func() { - Convey("Should include the default providers' options", func() { - cl := NewClient() - So(cl, ShouldNotBeNil) + t.Run("GetOption()", func(t *testing.T) { + assert.Nil(t, NewClient().GetOption("unknown-option-class"), "it should return nil for an unrecognized option") + assert.Nil(t, NewClient(nil).GetOption("unknown-option-class"), "it should ignore nil options") - cll, ok := cl.(*client) - So(ok, ShouldBeTrue) + opt := &testOption{} + assert.Equal(t, opt, NewClient(opt).GetOption("test"), "it should return an option if it is present") + assert.Equal(t, opt, NewClient(&testOption{}, opt).GetOption("test"), "it should return the most recent non-mergeable option") - opts := cll.fullDefaultOptions() - So(opts, ShouldNotBeNil) - So(opts, ShouldNotBeEmpty) + assert.Equal(t, &testMergeableOption{3}, NewClient(&testMergeableOption{1}, &testMergeableOption{2}).GetOption("test"), "it should merge options when they support it") + }) - i := 0 - for _, provider := range defaultOptionProviders { - opt := provider() - if opt == nil { - continue - } + t.Run("fullDefaultOptions()", func(t *testing.T) { + opts := cll.fullDefaultOptions() + assert.NotNil(t, opts, "the full options list should not be nil") + assert.NotEmpty(t, opts, "the full options list should not be empty") - Convey(fmt.Sprintf("%s (%d)", opt.Class(), i), func() { - So(opts[i], ShouldHaveSameTypeAs, opt) - }) + assert.Contains(t, opts, opt, "it should include the options passed to the client") - i++ + i := 0 + for _, provider := range defaultOptionProviders { + opt := provider() + if opt == nil { + continue } - }) - - Convey("With a root client", func() { - opt := &testOption{} - cl := NewClient(opt) - So(cl, ShouldNotBeNil) - - cll, ok := cl.(*client) - So(ok, ShouldBeTrue) + if i >= len(opts) { + t.Error("there are fewer options than there are providers which return option values") + break + } - So(cll.fullDefaultOptions(), ShouldContain, opt) - }) + assert.IsType(t, opt, opts[i], "Expected opts[%d] to have type %s but got %s instead", i, opt.Class(), opts[i].Class()) - Convey("With a derived client", func() { - opt1 := &testMergeableOption{data: 1} - opt2 := &testMergeableOption{data: 2} + i++ + } - cl := NewClient(opt1) - So(cl, ShouldNotBeNil) + opt1 := &testMergeableOption{data: 1} + opt2 := &testMergeableOption{data: 2} + cl := NewClient(opt1) + assert.NotNil(t, cl, "the client should not be nil") - dcl := cl.With(opt2) - So(dcl, ShouldNotBeNil) + dcl := cl.With(opt2) + assert.NotNil(t, dcl, "the derived client should not be nil") - cll, ok := dcl.(*client) - So(ok, ShouldBeTrue) + cll, ok := dcl.(*client) + assert.True(t, ok, "the derived client should actually be a *client") - opts := cll.fullDefaultOptions() - So(opts, ShouldContain, opt1) - So(opts, ShouldContain, opt2) - }) + opts = cll.fullDefaultOptions() + assert.Contains(t, opts, opt1, "the parent's options should be present in the list") + assert.Contains(t, opts, opt2, "the derive client's options should be present in the list") }) + } +} - Convey("Config Interface", func() { - Convey("DSN()", func() { - Convey("In a world with no DSNs", func() { - oldDefaultOptionProviders := defaultOptionProviders - defer func() { - defaultOptionProviders = oldDefaultOptionProviders - }() - - defaultOptionProviders = []func() Option{} - cl := NewClient() - So(cl, ShouldNotBeNil) - - cfg, ok := cl.(Config) - So(ok, ShouldBeTrue) - So(cfg.DSN(), ShouldEqual, "") - }) - - Convey("When someone has implemented their own custom DSN option", func() { - cl := NewClient(&testCustomClassOption{"sentry-go.dsn"}) - So(cl, ShouldNotBeNil) - - cfg, ok := cl.(Config) - So(ok, ShouldBeTrue) - So(cfg.DSN(), ShouldEqual, "") - }) - - Convey("With no custom DSN", func() { - cl := NewClient() - So(cl, ShouldNotBeNil) - - cfg, ok := cl.(Config) - So(ok, ShouldBeTrue) - - So(cfg.DSN(), ShouldEqual, "") - }) - - Convey("With a custom DSN", func() { - cl := NewClient(DSN("test")) - So(cl, ShouldNotBeNil) - - cfg, ok := cl.(Config) - So(ok, ShouldBeTrue) - - So(cfg.DSN(), ShouldEqual, "test") - }) - - Convey("With no custom DSN on a parent", func() { - cl := NewClient() - So(cl, ShouldNotBeNil) - - dcl := cl.With() - So(dcl, ShouldNotBeNil) - - cfg, ok := dcl.(Config) - So(ok, ShouldBeTrue) - - So(cfg.DSN(), ShouldEqual, "") - }) - - Convey("With a custom DSN on a parent", func() { - cl := NewClient(DSN("test")) - So(cl, ShouldNotBeNil) - - dcl := cl.With() - So(dcl, ShouldNotBeNil) - - cfg, ok := dcl.(Config) - So(ok, ShouldBeTrue) - So(cfg.DSN(), ShouldEqual, "test") - }) - }) - - Convey("SendQueue()", func() { - Convey("In a world with no SendQueues", func() { - oldDefaultOptionProviders := defaultOptionProviders - defer func() { - defaultOptionProviders = oldDefaultOptionProviders - }() - - defaultOptionProviders = []func() Option{} - cl := NewClient() - So(cl, ShouldNotBeNil) - - cfg, ok := cl.(Config) - So(ok, ShouldBeTrue) - - q := cfg.SendQueue() - So(q, ShouldNotBeNil) - So(q, ShouldHaveSameTypeAs, NewSequentialSendQueue(0)) - }) - - Convey("When someone has implemented their own custom SendQueue option", func() { - cl := NewClient(&testCustomClassOption{"sentry-go.sendqueue"}) - So(cl, ShouldNotBeNil) - - cfg, ok := cl.(Config) - So(ok, ShouldBeTrue) - - q := cfg.SendQueue() - So(q, ShouldNotBeNil) - So(q, ShouldHaveSameTypeAs, NewSequentialSendQueue(0)) - }) - - Convey("With no custom queue", func() { - cl := NewClient() - So(cl, ShouldNotBeNil) +func TestClientConfigInterface(t *testing.T) { + t.Run("DSN()", func (t *testing.T) { + t.Run("with no default DSN", func(t *testing.T) { + oldDefaultOptionProviders := defaultOptionProviders + defer func() { + defaultOptionProviders = oldDefaultOptionProviders + }() - cfg, ok := cl.(Config) - So(ok, ShouldBeTrue) + defaultOptionProviders = []func() Option{} - So(cfg.SendQueue(), ShouldEqual, DefaultClient().GetOption("sentry-go.sendqueue").(*sendQueueOption).queue) - }) + cl := NewClient() + assert.NotNil(t, cl, "the client should not be nil") - Convey("With a custom queue", func() { - q := NewSequentialSendQueue(0) - So(q, ShouldNotBeNil) + cfg, ok := cl.(Config) + assert.True(t, ok, "the client should implement the Config interface") + assert.Equal(t, "", cfg.DSN(), "the client should return an empty DSN") + }) - cl := NewClient(UseSendQueue(q)) - So(cl, ShouldNotBeNil) + t.Run("with a custom DSN option implementation", func(t *testing.T) { + cl := NewClient(&testCustomClassOption{"sentry-go.dsn"}) + assert.NotNil(t, cl, "the client should not be nil") - cfg, ok := cl.(Config) - So(ok, ShouldBeTrue) + cfg, ok := cl.(Config) + assert.True(t, ok, "the client should implement the Config interface") + assert.Equal(t, "", cfg.DSN(), "the client should return an empty DSN") + }) - So(cfg.SendQueue(), ShouldEqual, q) - }) + t.Run("with no custom DSN", func( t *testing.T) { + cl := NewClient() + assert.NotNil(t, cl, "the client should not be nil") - Convey("With no custom queue on a parent", func() { - cl := NewClient() - So(cl, ShouldNotBeNil) + cfg, ok := cl.(Config) + assert.True(t, ok, "the client should implement the Config interface") + assert.Equal(t, "", cfg.DSN(), "the client should return an empty DSN") + }) - dcl := cl.With() - So(dcl, ShouldNotBeNil) + t.Run("with a custom DSN", func( t *testing.T) { + cl := NewClient(DSN("test")) + assert.NotNil(t, cl, "the client should not be nil") - cfg, ok := dcl.(Config) - So(ok, ShouldBeTrue) + cfg, ok := cl.(Config) + assert.True(t, ok, "the client should implement the Config interface") + assert.Equal(t, "test", cfg.DSN(), "the client should return the DSN") + }) - So(cfg.SendQueue(), ShouldEqual, DefaultClient().GetOption("sentry-go.sendqueue").(*sendQueueOption).queue) - }) + t.Run("with a custom DSN on the parent", func( t *testing.T) { + cl := NewClient(DSN("test")) + assert.NotNil(t, cl, "the client should not be nil") - Convey("With a custom queue on a parent", func() { - q := NewSequentialSendQueue(0) - So(q, ShouldNotBeNil) + dcl := cl.With() + assert.NotNil(t, dcl, "the derived client should not be nil") - cl := NewClient(UseSendQueue(q)) - So(cl, ShouldNotBeNil) + cfg, ok := dcl.(Config) + assert.True(t, ok, "the derived client should implement the Config interface") + assert.Equal(t, "test", cfg.DSN(), "the derived client should return the DSN") + }) + }) - dcl := cl.With() - So(dcl, ShouldNotBeNil) + t.Run("SendQueue()", func(t *testing.T) { + t.Run("with no default send queue", func(t *testing.T) { + oldDefaultOptionProviders := defaultOptionProviders + defer func() { + defaultOptionProviders = oldDefaultOptionProviders + }() - cfg, ok := dcl.(Config) - So(ok, ShouldBeTrue) + defaultOptionProviders = []func() Option{} - So(cfg.SendQueue(), ShouldEqual, q) - }) - }) + cl := NewClient() + assert.NotNil(t, cl, "the client should not be nil") - Convey("Transport()", func() { - Convey("In a world with no Transports", func() { - oldDefaultOptionProviders := defaultOptionProviders - defer func() { - defaultOptionProviders = oldDefaultOptionProviders - }() + cfg, ok := cl.(Config) + assert.True(t, ok, "the client should implement the Config interface") + assert.NotNil(t, cfg.SendQueue(), "the client should not have a nil send queue") + assert.IsType(t, NewSequentialSendQueue(0), cfg.SendQueue(), "the client should default to the sequential send queue") + }) - defaultOptionProviders = []func() Option{} - cl := NewClient() - So(cl, ShouldNotBeNil) + t.Run("with a custom send queue option implementation", func(t *testing.T) { + cl := NewClient(&testCustomClassOption{"sentry-go.sendqueue"}) + assert.NotNil(t, cl, "the client should not be nil") - cfg, ok := cl.(Config) - So(ok, ShouldBeTrue) + cfg, ok := cl.(Config) + assert.True(t, ok, "the client should implement the Config interface") + assert.NotNil(t, cfg.SendQueue(), "the client should not have a nil send queue") + assert.IsType(t, NewSequentialSendQueue(0), cfg.SendQueue(), "the client should default to the sequential send queue") + }) - t := cfg.Transport() - So(t, ShouldNotBeNil) - So(t, ShouldHaveSameTypeAs, newHTTPTransport()) - }) + t.Run("with no custom send queue", func( t *testing.T) { + cl := NewClient() + assert.NotNil(t, cl, "the client should not be nil") - Convey("When someone has implemented their own custom Transport option", func() { - cl := NewClient(&testCustomClassOption{"sentry-go.transport"}) - So(cl, ShouldNotBeNil) + cfg, ok := cl.(Config) + assert.True(t, ok, "the client should implement the Config interface") + assert.NotNil(t, cfg.SendQueue(), "the client should not have a nil send queue") + assert.IsType(t, NewSequentialSendQueue(0), cfg.SendQueue(), "the client should default to the sequential send queue") + assert.Equal(t, DefaultClient().GetOption("sentry-go.sendqueue").(*sendQueueOption).queue, cfg.SendQueue(), "the client should use the global default send queue") + }) - cfg, ok := cl.(Config) - So(ok, ShouldBeTrue) + t.Run("with a custom send queue", func( t *testing.T) { + q := NewSequentialSendQueue(0) + cl := NewClient(UseSendQueue(q)) + assert.NotNil(t, cl, "the client should not be nil") - t := cfg.Transport() - So(t, ShouldNotBeNil) - So(t, ShouldHaveSameTypeAs, newHTTPTransport()) - }) + cfg, ok := cl.(Config) + assert.True(t, ok, "the client should implement the Config interface") + assert.NotNil(t, cfg.SendQueue(), "the client should not have a nil send queue") + assert.Equal(t, q, cfg.SendQueue(), "the client should use the configured send queue") + }) - Convey("With no custom transport", func() { - cl := NewClient() - So(cl, ShouldNotBeNil) + t.Run("with a custom send queue on the parent", func( t *testing.T) { + q := NewSequentialSendQueue(0) + cl := NewClient(UseSendQueue(q)) + assert.NotNil(t, cl, "the client should not be nil") - cfg, ok := cl.(Config) - So(ok, ShouldBeTrue) + dcl := cl.With() + assert.NotNil(t, dcl, "the derived client should not be nil") - So(cfg.Transport(), ShouldEqual, DefaultClient().GetOption("sentry-go.transport").(*transportOption).transport) - }) + cfg, ok := dcl.(Config) + assert.True(t, ok, "the derived client should implement the Config interface") + assert.NotNil(t, cfg.SendQueue(), "the client should not have a nil send queue") + assert.Equal(t, q, cfg.SendQueue(), "the client should use the configured send queue") + }) + }) - Convey("With a custom transport", func() { - t := newHTTPTransport() - So(t, ShouldNotBeNil) + t.Run("Transport()", func(t *testing.T) { + t.Run("with no default transports", func(t *testing.T) { + oldDefaultOptionProviders := defaultOptionProviders + defer func() { + defaultOptionProviders = oldDefaultOptionProviders + }() - cl := NewClient(UseTransport(t)) - So(cl, ShouldNotBeNil) + defaultOptionProviders = []func() Option{} - cfg, ok := cl.(Config) - So(ok, ShouldBeTrue) + cl := NewClient() + assert.NotNil(t, cl, "the client should not be nil") - So(cfg.Transport(), ShouldEqual, t) - }) + cfg, ok := cl.(Config) + assert.True(t, ok, "the client should implement the Config interface") + assert.NotNil(t, cfg.Transport(), "the client should not have a nil transport") + assert.IsType(t, newHTTPTransport(), cfg.Transport(), "the client should default to the HTTP transport") + }) - Convey("With no custom transport on a parent", func() { - cl := NewClient() - So(cl, ShouldNotBeNil) + t.Run("with a custom transport option implementation", func(t *testing.T) { + cl := NewClient(&testCustomClassOption{"sentry-go.transport"}) + assert.NotNil(t, cl, "the client should not be nil") - dcl := cl.With() - So(dcl, ShouldNotBeNil) + cfg, ok := cl.(Config) + assert.True(t, ok, "the client should implement the Config interface") + assert.NotNil(t, cfg.Transport(), "the client should not have a nil transport") + assert.IsType(t, newHTTPTransport(), cfg.Transport(), "the client should default to the HTTP transport") + }) - cfg, ok := dcl.(Config) - So(ok, ShouldBeTrue) + t.Run("with no custom transport", func( t *testing.T) { + cl := NewClient() + assert.NotNil(t, cl, "the client should not be nil") - So(cfg.Transport(), ShouldEqual, DefaultClient().GetOption("sentry-go.transport").(*transportOption).transport) - }) + cfg, ok := cl.(Config) + assert.True(t, ok, "the client should implement the Config interface") + assert.NotNil(t, cfg.Transport(), "the client should not have a nil transport") + assert.IsType(t, newHTTPTransport(), cfg.Transport(), "the client should default to the HTTP transport") + assert.Equal(t, DefaultClient().GetOption("sentry-go.transport").(*transportOption).transport, cfg.Transport(), "the client should use the global default transport") + }) - Convey("With a custom transport on a parent", func() { - t := newHTTPTransport() - So(t, ShouldNotBeNil) + t.Run("with a custom transport", func( t *testing.T) { + tr := newHTTPTransport() + cl := NewClient(UseTransport(tr)) + assert.NotNil(t, cl, "the client should not be nil") - cl := NewClient(UseTransport(t)) - So(cl, ShouldNotBeNil) + cfg, ok := cl.(Config) + assert.True(t, ok, "the client should implement the Config interface") + assert.NotNil(t, cfg.Transport(), "the client should not have a nil transport") + assert.Equal(t, tr, cfg.Transport(), "the client should use the configured transport") + }) - dcl := cl.With() - So(dcl, ShouldNotBeNil) + t.Run("with a custom transport on the parent", func( t *testing.T) { + tr := newHTTPTransport() + cl := NewClient(UseTransport(tr)) + assert.NotNil(t, cl, "the client should not be nil") - cfg, ok := dcl.(Config) - So(ok, ShouldBeTrue) + dcl := cl.With() + assert.NotNil(t, dcl, "the derived client should not be nil") - So(cfg.Transport(), ShouldEqual, t) - }) - }) + cfg, ok := dcl.(Config) + assert.True(t, ok, "the derived client should implement the Config interface") + assert.NotNil(t, cfg.Transport(), "the client should not have a nil transport") + assert.Equal(t, tr, cfg.Transport(), "the client should use the configured transport") }) }) } diff --git a/contexts_test.go b/contexts_test.go index cc56ce0..2ba78ac 100644 --- a/contexts_test.go +++ b/contexts_test.go @@ -4,7 +4,7 @@ import ( "runtime" "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleRuntimeContext() { @@ -60,149 +60,121 @@ func ExampleDeviceContext() { ) } -func TestContexts(t *testing.T) { - Convey("Contexts", t, func() { - Convey("RuntimeContext()", func() { - Convey("Should return a context option", func() { - So(RuntimeContext("go", runtime.Version()), ShouldHaveSameTypeAs, Context("runtime", nil)) - }) - - Convey("Should set the context type to 'runtime'", func() { - c := RuntimeContext("go", runtime.Version()) - cc, ok := c.(*contextOption) - So(ok, ShouldBeTrue) - So(cc.contexts, ShouldContainKey, "runtime") - }) - - Convey("Should set the context correctly", func() { - c := RuntimeContext("go", runtime.Version()) - cc, ok := c.(*contextOption) - So(ok, ShouldBeTrue) - So(cc.contexts["runtime"], ShouldResemble, map[string]string{ - "name": "go", - "version": runtime.Version(), - }) - }) - }) +func TestRuntimeContext(t *testing.T) { + c := RuntimeContext("go", runtime.Version()) + + assert.NotNil(t, c, "it should not return a nil option") + assert.IsType(t, Context("runtime", nil), c, "it should return the same thing as a Context()") + + cc, ok := c.(*contextOption) + assert.True(t, ok, "it should actually return a *contextOption") + if assert.Contains(t, cc.contexts, "runtime", "it should specify a runtime context") { + assert.Equal(t, map[string]string{ + "name": "go", + "version": runtime.Version(), + }, cc.contexts["runtime"], "it should specify the correct context values") + } +} - Convey("OSContext()", func() { - osInfo := OSContextInfo{ - Version: "CentOS 7.3", - Build: "centos7.3.1611", - KernelVersion: "3.10.0-514", - Rooted: false, - } - - Convey("Should return a context option", func() { - So(OSContext(&osInfo), ShouldHaveSameTypeAs, Context("os", nil)) - }) - - Convey("Should set the context type to 'os'", func() { - c := OSContext(&osInfo) - cc, ok := c.(*contextOption) - So(ok, ShouldBeTrue) - So(cc.contexts, ShouldContainKey, "os") - }) - - Convey("Should set the context correctly", func() { - c := OSContext(&osInfo) - cc, ok := c.(*contextOption) - So(ok, ShouldBeTrue) - So(cc.contexts["os"], ShouldResemble, &osInfo) - }) - }) +func TestOSContext(t *testing.T) { + osInfo := OSContextInfo{ + Version: "CentOS 7.3", + Build: "centos7.3.1611", + KernelVersion: "3.10.0-514", + Rooted: false, + } - Convey("DeviceContext()", func() { - deviceInfo := DeviceContextInfo{ - Architecture: "arm", - BatteryLevel: 100, - Family: "Samsung Galaxy", - Model: "Samsung Galaxy S8", - ModelID: "SM-G95550", - Name: "Samsung Galaxy S8", - Orientation: "portrait", - } - - Convey("Should return a context option", func() { - So(DeviceContext(&deviceInfo), ShouldHaveSameTypeAs, Context("device", nil)) - }) - - Convey("Should set the context type to 'device'", func() { - c := DeviceContext(&deviceInfo) - cc, ok := c.(*contextOption) - So(ok, ShouldBeTrue) - So(cc.contexts, ShouldContainKey, "device") - }) - - Convey("Should set the context correctly", func() { - c := DeviceContext(&deviceInfo) - cc, ok := c.(*contextOption) - So(ok, ShouldBeTrue) - So(cc.contexts["device"], ShouldResemble, &deviceInfo) - }) - }) + c := OSContext(&osInfo) - Convey("Context()", func() { - c := Context("test", "data") - So(c, ShouldNotBeNil) - So(c, ShouldHaveSameTypeAs, &contextOption{}) + assert.NotNil(t, c, "it should not return a nil option") + assert.IsType(t, Context("os", nil), c, "it should return the same thing as a Context()") + + cc, ok := c.(*contextOption) + assert.True(t, ok, "it should actually return a *contextOption") + if assert.Contains(t, cc.contexts, "os", "it should specify an os context") { + assert.Equal(t, &osInfo, cc.contexts["os"], "it should specify the correct context values") + } +} - cc := c.(*contextOption) - So(cc.contexts, ShouldContainKey, "test") - So(cc.contexts["test"], ShouldEqual, "data") - }) +func TestDeviceContext(t *testing.T) { + deviceInfo := DeviceContextInfo{ + Architecture: "arm", + BatteryLevel: 100, + Family: "Samsung Galaxy", + Model: "Samsung Galaxy S8", + ModelID: "SM-G95550", + Name: "Samsung Galaxy S8", + Orientation: "portrait", + } + + c := DeviceContext(&deviceInfo) + + assert.NotNil(t, c, "it should not return a nil option") + assert.IsType(t, Context("device", nil), c, "it should return the same thing as a Context()") + + cc, ok := c.(*contextOption) + assert.True(t, ok, "it should actually return a *contextOption") + if assert.Contains(t, cc.contexts, "device", "it should specify an os context") { + assert.Equal(t, &deviceInfo, cc.contexts["device"], "it should specify the correct context values") + } +} - Convey("ContextOption", func() { - c := Context("test", "data") - So(c, ShouldNotBeNil) - - Convey("Should have the correct Class()", func() { - So(c.Class(), ShouldEqual, "contexts") - }) - - Convey("Should implement MergableOption interface", func() { - So(c, ShouldImplement, (*MergeableOption)(nil)) - }) - - Convey("Merge()", func() { - cc, ok := c.(*contextOption) - So(ok, ShouldBeTrue) - - Convey("Should overwrite if it cannot identify the old type", func() { - out := cc.Merge(&testOption{}) - So(out, ShouldEqual, c) - }) - - Convey("Should overwriting old fields", func() { - old := Context("test", "oldData") - out := cc.Merge(old) - So(out, ShouldNotBeNil) - So(out, ShouldHaveSameTypeAs, &contextOption{}) - - So(out.(*contextOption).contexts, ShouldResemble, map[string]interface{}{ - "test": "data", - }) - }) - - Convey("Should add new fields", func() { - old := Context("old", "data") - out := cc.Merge(old) - So(out, ShouldNotBeNil) - So(out, ShouldHaveSameTypeAs, &contextOption{}) - - So(out.(*contextOption).contexts, ShouldResemble, map[string]interface{}{ - "old": "data", - "test": "data", - }) - }) - }) - - Convey("MarshalJSON", func() { - c := Context("test", "data") - So(testOptionsSerialize(c), ShouldResemble, map[string]interface{}{ - "test": "data", - }) - }) +func TestContext(t *testing.T) { + c := Context("test", "data") + assert.NotNil(t, c, "it should not return a nil option") + assert.IsType(t, &contextOption{}, c, "it should actually return a *contextOption") + + cc := c.(*contextOption) + assert.Contains(t, cc.contexts, "test", "it should set the 'test' context") + assert.Equal(t, "data", cc.contexts["test"], "it should set the context data correctly") +} + +func TestContextOption(t *testing.T) { + c := Context("test", "data") + assert.NotNil(t, c, "it should not return a nil option") + + assert.IsType(t, &contextOption{}, c, "it should actually return a *contextOption") + cc := c.(*contextOption) + + assert.Equal(t, "contexts", c.Class(), "it should use the right option class") + assert.Implements(t, (*MergeableOption)(nil), c, "it should implement the MergeableOption interface") + + t.Run("Merge()", func(t *testing.T) { + t.Run("Unknown Type", func(t *testing.T) { + out := cc.Merge(&testOption{}) + assert.Equal(t, c, out, "it should overwrite the original value") }) + + t.Run("Existing Context", func(t *testing.T) { + old := Context("test", "oldData") + out := cc.Merge(old) + + assert.NotNil(t, out, "it should not return a nil result") + assert.IsType(t, &contextOption{}, out, "it should return a new *contextOption") + + oo := out.(*contextOption) + assert.Equal(t, map[string]interface{}{ + "test": "data", + }, oo.contexts) + }) + + t.Run("Existing Different Context", func(t *testing.T) { + old := Context("old", "oldData") + out := cc.Merge(old) + + assert.NotNil(t, out, "it should not return a nil result") + assert.IsType(t, &contextOption{}, out, "it should return a new *contextOption") + + oo := out.(*contextOption) + assert.Equal(t, map[string]interface{}{ + "test": "data", + "old": "oldData", + }, oo.contexts) + }) + }) + + t.Run("MarshalJSON()", func(t *testing.T) { + c := Context("test", "data") + assert.Equal(t, map[string]interface{}{ "test": "data" }, testOptionsSerialize(t, c)) }) } diff --git a/culprit_test.go b/culprit_test.go index df6fabf..39b5ab6 100644 --- a/culprit_test.go +++ b/culprit_test.go @@ -3,7 +3,7 @@ package sentry import ( "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleCulprit() { @@ -19,19 +19,8 @@ func ExampleCulprit() { } func TestCulprit(t *testing.T) { - Convey("Culprit", t, func() { - Convey("Culprit()", func() { - Convey("Should return an Option", func() { - So(Culprit("test"), ShouldImplement, (*Option)(nil)) - }) - }) - - Convey("Should use the correct class", func() { - So(Culprit("test").Class(), ShouldEqual, "culprit") - }) - - Convey("MarshalJSON", func() { - So(testOptionsSerialize(Culprit("test")), ShouldResemble, "test") - }) - }) + c := Culprit("test") + assert.Implements(t, (*Option)(nil), c, "it should implement the Option interface") + assert.Equal(t, "culprit", c.Class(), "it should use the correct option class") + assert.Equal(t, "test", testOptionsSerialize(t, c), "it should serialize to the value which was passed in the constructor") } diff --git a/dsn_test.go b/dsn_test.go index b2db567..1009937 100644 --- a/dsn_test.go +++ b/dsn_test.go @@ -3,7 +3,7 @@ package sentry import ( "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleDSN() { @@ -20,56 +20,38 @@ func ExampleDSN() { } func TestDSN(t *testing.T) { - Convey("DSN", t, func() { - Convey("Parse()", func() { - cases := []struct { - Name string - URL string - Error error - }{ - {"With a valid URL", "https://u:p@example.com/sentry/1", nil}, - {"With a badly formatted URL", ":", ErrBadURL}, - {"Without a public key", "https://example.com/sentry/1", ErrMissingPublicKey}, - {"Without a private key", "https://u@example.com/sentry/1", nil}, - {"Without a project ID", "https://u:p@example.com", ErrMissingProjectID}, - } - - for _, tc := range cases { - Convey(tc.Name, func() { - d := &dsn{} - err := d.Parse(tc.URL) - if tc.Error == nil { - So(err, ShouldBeNil) - } else { - So(err, ShouldNotBeNil) - So(err.Error(), ShouldStartWith, tc.Error.Error()) - } - }) - } - }) - - Convey("AuthHeader()", func() { - Convey("With no public key", func() { - d := &dsn{ - PrivateKey: "secret", - } - So(d.AuthHeader(), ShouldEqual, "") - }) - - Convey("With no private key", func() { - d := &dsn{ - PublicKey: "key", + t.Run("Parse()", func (t *testing.T) { + cases := []struct { + Name string + URL string + Error error + }{ + {"With a valid URL", "https://u:p@example.com/sentry/1", nil}, + {"With a badly formatted URL", ":", ErrBadURL}, + {"Without a public key", "https://example.com/sentry/1", ErrMissingPublicKey}, + {"Without a private key", "https://u@example.com/sentry/1", nil}, + {"Without a project ID", "https://u:p@example.com", ErrMissingProjectID}, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + d := &dsn{} + err := d.Parse(tc.URL) + + if tc.Error == nil { + assert.Nil(t, err, "it should not return an error") + } else { + assert.NotNil(t, err, "it should return an error") + assert.Regexp(t, "^" + tc.Error.Error(), err.Error(), "it should return the right error") } - So(d.AuthHeader(), ShouldEqual, "Sentry sentry_version=4, sentry_key=key") }) + } + }) - Convey("With valid keys", func() { - d := &dsn{ - PublicKey: "key", - PrivateKey: "secret", - } - So(d.AuthHeader(), ShouldEqual, "Sentry sentry_version=4, sentry_key=key, sentry_secret=secret") - }) - }) + t.Run("AuthHeader()", func(t *testing.T) { + assert.Equal(t, "", (&dsn{PrivateKey: "secret"}).AuthHeader(), "should return no auth header if no public key is provided") + assert.Equal(t, "Sentry sentry_version=4, sentry_key=key", (&dsn{PublicKey: "key"}).AuthHeader(), "should return an auth header with just the public key if no private key is provided") + assert.Equal(t, "Sentry sentry_version=4, sentry_key=key, sentry_secret=secret", (&dsn{PublicKey: "key", PrivateKey: "secret"}).AuthHeader(), "should return a full auth header both the public and private key are provided") }) } diff --git a/environment_test.go b/environment_test.go index 3e6f119..e3bae59 100644 --- a/environment_test.go +++ b/environment_test.go @@ -4,7 +4,7 @@ import ( "os" "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleEnvironment() { @@ -20,39 +20,45 @@ func ExampleEnvironment() { } func TestEnvironment(t *testing.T) { - Convey("Environment", t, func() { - Convey("Should register with the default providers", func() { - Convey("If the ENV environment variable is set", func() { - os.Setenv("ENV", "testing") - defer os.Unsetenv("ENV") - - opt := testGetOptionsProvider(&environmentOption{}) - So(opt, ShouldNotBeNil) - So(opt.(*environmentOption).env, ShouldEqual, "testing") - }) - - Convey("If the ENVIRONMENT environment variable is set", func() { - os.Setenv("ENVIRONMENT", "testing") - defer os.Unsetenv("ENVIRONMENT") - - opt := testGetOptionsProvider(&environmentOption{}) - So(opt, ShouldNotBeNil) - So(opt.(*environmentOption).env, ShouldEqual, "testing") - }) - }) - - Convey("Environment()", func() { - Convey("Should return an Option", func() { - So(Environment("testing"), ShouldImplement, (*Option)(nil)) - }) - }) - - Convey("Should use the correct class", func() { - So(Environment("test").Class(), ShouldEqual, "environment") - }) - - Convey("MarshalJSON", func() { - So(testOptionsSerialize(Environment("test")), ShouldResemble, "test") - }) + o := Environment("testing") + assert.NotNil(t, o, "it should not return a nil option") + assert.Implements(t, (*Option)(nil), o, "it should implement the option interface") + assert.Equal(t, "environment", o.Class(), "it should use the correct option class") + + t.Run("No Environment", func(t *testing.T) { + os.Unsetenv("ENV") + os.Unsetenv("ENVIRONMENT") + + opt := testGetOptionsProvider(t, &environmentOption{}) + assert.Nil(t, opt, "it should not be registered as a default option provider") + }) + + t.Run("$ENV=...", func(t *testing.T){ + os.Setenv("ENV", "testing") + defer os.Unsetenv("ENV") + + opt := testGetOptionsProvider(t, &environmentOption{}) + assert.NotNil(t, opt, "it should be registered with the default option providers") + assert.IsType(t, &environmentOption{}, opt, "it should actually be an *environmentOption") + + oo := opt.(*environmentOption) + assert.Equal(t, "testing", oo.env, "it should set the environment to the same value as the $ENV variable") + }) + + t.Run("$ENVIRONMENT=...", func(t *testing.T){ + os.Setenv("ENVIRONMENT", "testing") + defer os.Unsetenv("ENVIRONMENT") + + opt := testGetOptionsProvider(t, &environmentOption{}) + assert.NotNil(t, opt, "it should be registered with the default option providers") + assert.IsType(t, &environmentOption{}, opt, "it should actually be an *environmentOption") + + oo := opt.(*environmentOption) + assert.Equal(t, "testing", oo.env, "it should set the environment to the same value as the $ENVIRONMENT variable") + }) + + t.Run("MarshalJSON()", func(t *testing.T) { + s := testOptionsSerialize(t, o) + assert.Equal(t, "testing", s, "it should serialize to the name of the environment") }) } diff --git a/errortype_test.go b/errortype_test.go index 76c30ac..aa838b4 100644 --- a/errortype_test.go +++ b/errortype_test.go @@ -4,20 +4,23 @@ import ( "testing" "github.com/pkg/errors" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func TestErrType(t *testing.T) { - Convey("ErrType", t, func() { - const errType = ErrType("sentry: this is a test error") + const errType = ErrType("sentry: this is a test error") + assert.True(t, errType.IsInstance(errType), "it should be an instance of itself") + assert.True(t, errType.IsInstance(errors.New(errType.Error())), "errors with the same message should be an instance of this error") + assert.EqualError(t, errType, "sentry: this is a test error", "it should report the correct error message") - Convey("IsInstance()", func() { - So(errType.IsInstance(errType), ShouldBeTrue) - So(errType.IsInstance(errors.New(errType.Error())), ShouldBeTrue) - }) + type UnwrappableError interface { + Unwrap() error + } - Convey("Error()", func() { - So(errType.Error(), ShouldEqual, "sentry: this is a test error") - }) - }) + if assert.Implements(t, (*UnwrappableError)(nil), errType, "it should implement the Unwrap() method") { + var err error + err = errType + + assert.Nil(t, err.(UnwrappableError).Unwrap(), "unwrapping the error should return nil") + } } diff --git a/eventID_test.go b/eventID_test.go index 6b5c8c2..7a9c404 100644 --- a/eventID_test.go +++ b/eventID_test.go @@ -4,7 +4,7 @@ import ( "log" "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleEventID() { @@ -29,63 +29,40 @@ func ExampleEventID() { } func TestEventID(t *testing.T) { - Convey("EventID", t, func() { - Convey("NewEventID()", func() { - Convey("Should return a valid event ID", func() { - id, err := NewEventID() - So(err, ShouldBeNil) - So(id, ShouldHaveLength, 32) - - for _, r := range id { - So(r, ShouldBeIn, []rune("0123456789abcdef")) - } - }) - }) - - id, err := NewEventID() - So(err, ShouldBeNil) + id, err := NewEventID() + assert.Nil(t, err, "creating an event ID shouldn't return an error") + assert.Regexp(t, "^[0-9a-f]{32}$", id, "the event ID should be 32 characters long and only alphanumeric characters") - Convey("EventID()", func() { - id, err := NewEventID() - So(err, ShouldBeNil) + t.Run("EventID()", func(t *testing.T) { + assert.Nil(t, EventID("invalid"), "it should return nil if the ID has the wrong length") + assert.Nil(t, EventID("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"), "it should return nil if the ID contains invalid characters") - Convey("Should return an Option", func() { - So(EventID(id), ShouldImplement, (*Option)(nil)) - }) + o := EventID(id) + assert.NotNil(t, o, "it should return a non-nil option if the ID is valid") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") - Convey("Should return nil if the ID is invalid", func() { - So(EventID("invalid"), ShouldBeNil) - So(EventID("xx23456789abcdef0123456789abcdef"), ShouldBeNil) - }) - }) + assert.Equal(t, "event_id", o.Class(), "it should use the correct option class") - Convey("Should use the correct class", func() { - So(EventID(id).Class(), ShouldEqual, "event_id") + t.Run("MarshalJSON()", func(t *testing.T) { + assert.Equal(t, id, testOptionsSerialize(t, EventID(id)), "it should serialize to the ID") }) + }) - Convey("MarshalJSON", func() { - So(testOptionsSerialize(EventID(id)), ShouldResemble, id) - }) - - Convey("Packet Extensions", func() { - Convey("getEventID()", func() { - Convey("With an EventID", func() { - p := NewPacket().SetOptions(EventID(id)) - pp, ok := p.(*packet) - So(ok, ShouldBeTrue) - - So(pp.getEventID(), ShouldEqual, id) - }) + t.Run("Packet Extensions", func(t *testing.T) { + t.Run("getEventID()", func(t *testing.T) { + p := NewPacket() + assert.NotNil(t, p, "the packet should not be nil") - Convey("Without an EventID", func() { - p := NewPacket() - pp, ok := p.(*packet) - So(ok, ShouldBeTrue) + pp, ok := p.(*packet) + assert.True(t, ok, "the packet should actually be a *packet") + assert.Equal(t, "", pp.getEventID(), "it should return an empty event ID if there is no EventID option") - So(pp.getEventID(), ShouldEqual, "") - }) - }) + p = NewPacket().SetOptions(EventID(id)) + assert.NotNil(t, p, "the packet should not be nil") + pp, ok = p.(*packet) + assert.True(t, ok, "the packet should actually be a *packet") + assert.Equal(t, id, pp.getEventID(), "it should return the event ID") }) }) } diff --git a/exception.go b/exception.go index cb8a125..398e7f8 100644 --- a/exception.go +++ b/exception.go @@ -53,6 +53,10 @@ func (e *ExceptionInfo) ForError(err error) *ExceptionInfo { // ExceptionForError allows you to include the details of an error which // occurred within your application as part of the event you send to Sentry. func ExceptionForError(err error) Option { + if err == nil { + return nil + } + exceptions := []*ExceptionInfo{} for err != nil { @@ -108,7 +112,7 @@ func (o *exceptionOption) Merge(old Option) Option { func (o *exceptionOption) Finalize() { for _, ex := range o.Exceptions { if ex.StackTrace != nil { - if finalize, ok := ex.StackTrace.(FinalizableOption); ok { + if finalize, ok := ex.StackTrace.(FinalizeableOption); ok { finalize.Finalize() } } diff --git a/exception_go1.13_test.go b/exception_go1.13_test.go new file mode 100644 index 0000000..7f17b9d --- /dev/null +++ b/exception_go1.13_test.go @@ -0,0 +1,28 @@ +// +build go1.13 + +package sentry + +import ( + "testing" + "fmt" + + "github.com/stretchr/testify/assert" +) + +func TestExceptionForErrorWrappingGo113(t *testing.T) { + t.Run("fmt.Errorf()", func(t *testing.T) { + err := fmt.Errorf("root cause") + err = fmt.Errorf("cause 1: %w", err) + err = fmt.Errorf("cause 2: %w", err) + err = fmt.Errorf("example error: %w", err) + + e := ExceptionForError(err) + assert.NotNil(t, e, "it should return a non-nil option") + + exx, ok := e.(*exceptionOption) + assert.True(t, ok, "the option should actually be a *exceptionOption") + + assert.Len(t, exx.Exceptions, 4) + assert.Equal(t, "root cause", exx.Exceptions[0].Value) + }) +} \ No newline at end of file diff --git a/exception_test.go b/exception_test.go index 6b64f04..4c31293 100644 --- a/exception_test.go +++ b/exception_test.go @@ -5,184 +5,161 @@ import ( "testing" "github.com/pkg/errors" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func TestException(t *testing.T) { - Convey("Exception", t, func() { - Convey("Exception()", func() { - ex := NewExceptionInfo() - Convey("Should return an Option", func() { - So(Exception(ex), ShouldImplement, (*Option)(nil)) - }) - - Convey("Should use the correct Class()", func() { - So(Exception(ex).Class(), ShouldEqual, "exception") - }) - - Convey("Merge()", func() { - Convey("Should append newer exceptions to the list", func() { - exNew := NewExceptionInfo() - - exo1 := Exception(ex) - exo2 := Exception(exNew) - - mergable, ok := exo2.(MergeableOption) - So(ok, ShouldBeTrue) - exo3 := mergable.Merge(exo1) - So(exo3, ShouldNotBeNil) - So(exo3, ShouldHaveSameTypeAs, exo1) - - exx, ok := exo3.(*exceptionOption) - So(ok, ShouldBeTrue) - So(exx.Exceptions, ShouldHaveLength, 2) - So(exx.Exceptions[0], ShouldEqual, ex) - So(exx.Exceptions[1], ShouldEqual, exNew) - }) - - Convey("Should overwrite if it doesn't recognize the old option", func() { - exo := Exception(ex) - mergable, ok := exo.(MergeableOption) - So(ok, ShouldBeTrue) - - So(mergable.Merge(&testOption{}), ShouldEqual, exo) - }) - }) - - Convey("Finalize()", func() { - Convey("Should call finalize on all of its exception's stacktraces", func() { - err := errors.New("test error") - So(err, ShouldNotBeNil) - - ex := ExceptionForError(err) - So(ex, ShouldNotBeNil) - - exx, ok := ex.(*exceptionOption) - So(ok, ShouldBeTrue) - - So(exx.Exceptions, ShouldHaveLength, 1) - - st := exx.Exceptions[0].StackTrace - So(st, ShouldNotBeNil) - st.WithInternalPrefixes("github.com/SierraSoftworks/sentry-go") - - sti, ok := st.(*stackTraceOption) - So(ok, ShouldBeTrue) - So(sti.Frames, ShouldNotBeEmpty) - - hasInternal := false - for _, frame := range sti.Frames { - if frame.InApp { - hasInternal = true - } - } - So(hasInternal, ShouldBeFalse) - - exx.Finalize() - - hasInternal = false - for _, frame := range sti.Frames { - if frame.InApp { - hasInternal = true - } - } - So(hasInternal, ShouldBeTrue) - }) - }) + ex := NewExceptionInfo() + assert.NotNil(t, ex, "the exception info should not be nil") + + e := Exception(ex) + assert.NotNil(t, e, "the exception option should not be nil") + assert.Implements(t, (*Option)(nil), e, "it should implement the Option interface") + assert.Equal(t, "exception", e.Class(), "it should use the correct option class") + + exx, ok := e.(*exceptionOption) + assert.True(t, ok, "the exception option should actually be an *exceptionOption") + + t.Run("Merge()", func(t *testing.T) { + assert.Implements(t, (*MergeableOption)(nil), e, "it should implement the MergeableOption interface") + + ex2 := NewExceptionInfo() + e2 := Exception(ex2) + + mergeable, ok := e2.(MergeableOption) + assert.True(t, ok, "the exception option should be mergeable") + + e3 := mergeable.Merge(e) + assert.NotNil(t, e3, "the resulting merged exception option should not be nil") + assert.IsType(t, e, e3, "the resulting merged exception option should be the same type as the original option") + + exx, ok := e3.(*exceptionOption) + assert.True(t, ok, "the resulting merged exception option should actually be a *exceptionOption") + + if assert.Len(t, exx.Exceptions, 2, "it should contain both exceptions") { + assert.Equal(t, ex, exx.Exceptions[0], "the first exception should be the first exception we found") + assert.Equal(t, ex2, exx.Exceptions[1], "the second exception should be the second exception we found") + } + + e3 = mergeable.Merge(&testOption{}) + assert.Equal(t, e2, e3, "if the other option is not an exception option then it should be replaced") + }) + + t.Run("Finalize()", func(t *testing.T) { + assert.Implements(t, (*FinalizeableOption)(nil), e, "it should implement the FinalizeableOption interface") + + assert.Len(t, exx.Exceptions, 1, "one exception should be registered") + + st := exx.Exceptions[0].StackTrace + assert.NotNil(t, st, "the exception shoudl have a stacktrace") + st.WithInternalPrefixes("github.com/SierraSoftworks/sentry-go") + + sti, ok := st.(*stackTraceOption) + assert.True(t, ok, "the stacktrace should actually be a *stackTraceOption") + assert.NotEmpty(t, sti.Frames, "the stacktrace should include stack frames") + + hasInternal := false + for _, frame := range sti.Frames { + if frame.InApp { + hasInternal = true + } + } + assert.False(t, hasInternal, "the internal stack frames should not have been processed yet") + + exx.Finalize() + + hasInternal = false + for _, frame := range sti.Frames { + if frame.InApp { + hasInternal = true + } + } + assert.True(t, hasInternal, "the internal stack frames should have been identified now") + }) + + t.Run("MarshalJSON()", func(t *testing.T) { + serialized := testOptionsSerialize(t, Exception(&ExceptionInfo{ + Type: "TestException", + Value: "This is a test", + })) + + assert.Equal(t, map[string]interface{}{ + "values": []interface{}{ + map[string]interface{}{ + "type": "TestException", + "value": "This is a test", + }, + }, + }, serialized) + }) +} + +func TestExceptionForError(t *testing.T) { + assert.Nil(t, ExceptionForError(nil), "it should return nil if the error is nil") + + err := fmt.Errorf("example error") + e := ExceptionForError(err) + assert.NotNil(t, e, "it should return a non-nil option") + assert.Implements(t, (*Option)(nil), e, "it should implement the Option interface") + + t.Run("github.com/pkg/errors", func(t *testing.T) { + err := errors.New("root cause") + err = errors.Wrap(err, "cause 1") + err = errors.Wrap(err, "cause 2") + err = errors.Wrap(err, "example error") + + e := ExceptionForError(err) + assert.NotNil(t, e, "it should return a non-nil option") + + exx, ok := e.(*exceptionOption) + assert.True(t, ok, "the option should actually be a *exceptionOption") + + // errors.Wrap adds two entries to the cause heirarchy + // 1 - withMessage{} + // 2 - withStack{} + assert.Len(t, exx.Exceptions, 1 + (3*2)) + assert.Equal(t, "root cause", exx.Exceptions[0].Value) + }) +} + +func TestExceptionInfo(t *testing.T) { + t.Run("NewExceptionInfo()", func (t *testing.T) { + ex := NewExceptionInfo() + assert.NotNil(t, ex, "it should not return nil") + assert.Equal(t, "unknown", ex.Type, "it should report an 'unknown' type by default") + assert.Equal(t, "An unknown error has occurred", ex.Value, "it should report a default error message") + assert.NotNil(t, ex.StackTrace, "it should contain a stack trace") + }) + + t.Run("ForError()", func(t *testing.T) { + ex := NewExceptionInfo() + assert.NotNil(t, ex, "it should not return nil") + + assert.Equal(t, ex, ex.ForError(fmt.Errorf("example error")), "it should return the same exception info object for chaining") + assert.Equal(t, "example error", ex.Type, "it should load the type from the error") + assert.Equal(t, "example error", ex.Value, "it should load the message from the error") + assert.Equal(t, "", ex.Module, "it should load the module from the error") + + t.Run("with no stacktrace", func(t *testing.T) { + ex := &ExceptionInfo{} + ex.ForError(fmt.Errorf("example error")) + assert.NotNil(t, ex.StackTrace, "it should use the location of the current call as the stack trace") }) - Convey("ExceptionForError()", func() { - Convey("Should return an Option", func() { - err := fmt.Errorf("example error") - So(ExceptionForError(err), ShouldImplement, (*Option)(nil)) - }) - - Convey("With wrapped errors", func() { - err := errors.New("root cause") - err = errors.Wrap(err, "cause 1") - err = errors.Wrap(err, "cause 2") - err = errors.Wrap(err, "example error") - - ex := ExceptionForError(err) - So(ex, ShouldNotBeNil) - - exx, ok := ex.(*exceptionOption) - So(ok, ShouldBeTrue) - - // errors.Wrap adds two entries to the cause heirarchy - // 1 - withMessage{} - // 2 - withStack{} - So(exx.Exceptions, ShouldHaveLength, 1+(3*2)) - So(exx.Exceptions[0].Value, ShouldEqual, "root cause") - }) + t.Run("with a fmt.Errorf() error", func(t *testing.T) { + assert.NotNil(t, ex.StackTrace, "it should use the location of the current call as the stack trace") }) - Convey("ExceptionInfo", func() { - Convey("NewExceptionInfo()", func() { - ex := NewExceptionInfo() - So(ex, ShouldNotBeNil) - So(ex.Type, ShouldEqual, "unknown") - So(ex.Value, ShouldEqual, "An unknown error has occurred") - So(ex.StackTrace, ShouldNotBeNil) - }) - - Convey("ForError()", func() { - ex := NewExceptionInfo() - So(ex, ShouldNotBeNil) - - Convey("Without an existing StackTrace", func() { - ex := &ExceptionInfo{} - err := fmt.Errorf("example error") - So(ex.ForError(err), ShouldEqual, ex) - So(ex.Type, ShouldEqual, "example error") - So(ex.Module, ShouldEqual, "") - So(ex.Value, ShouldEqual, "example error") - So(ex.StackTrace, ShouldNotBeNil) - }) - - Convey("With a normal error", func() { - err := fmt.Errorf("example error") - So(ex.ForError(err), ShouldEqual, ex) - So(ex.Type, ShouldEqual, "example error") - So(ex.Module, ShouldEqual, "") - So(ex.Value, ShouldEqual, "example error") - So(ex.StackTrace, ShouldNotBeNil) - }) - - Convey("With a stacktraceable error", func() { - err := errors.New("example error") - So(ex.ForError(err), ShouldEqual, ex) - So(ex.Module, ShouldEqual, "") - So(ex.Type, ShouldEqual, "example error") - So(ex.Value, ShouldEqual, "example error") - So(ex.StackTrace, ShouldNotBeNil) - }) - - Convey("With a well formatted message", func() { - err := errors.New("test: example error") - So(ex.ForError(err), ShouldEqual, ex) - So(ex.Module, ShouldEqual, "test") - So(ex.Type, ShouldEqual, "example error") - So(ex.Value, ShouldEqual, "test: example error") - }) - }) + t.Run("with a github.com/pkg/errors error", func(t *testing.T) { + ex.ForError(errors.New("example error")) + assert.NotNil(t, ex.StackTrace, "it should use the location of the error as the stack trace") }) - Convey("MarshalJSON", func() { - Convey("Should marshal correctly", func() { - serialized := testOptionsSerialize(Exception(&ExceptionInfo{ - Type: "TestException", - Value: "This is a test", - })) - So(serialized, ShouldResemble, map[string]interface{}{ - "values": []interface{}{ - map[string]interface{}{ - "type": "TestException", - "value": "This is a test", - }, - }, - }) - }) + t.Run("with a structured error message", func(t *testing.T) { + ex.ForError(fmt.Errorf("test: example error")) + assert.Equal(t, "test: example error", ex.Value, "it should load the message from the error") + assert.Equal(t, "example error", ex.Type, "it should load the type from the error") + assert.Equal(t, "test", ex.Module, "it should load the module from the error") }) }) } diff --git a/extra_test.go b/extra_test.go index 0c6d42d..d1a27b1 100644 --- a/extra_test.go +++ b/extra_test.go @@ -3,7 +3,7 @@ package sentry import ( "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleExtra() { @@ -31,110 +31,76 @@ func ExampleExtra() { } func TestExtra(t *testing.T) { - Convey("Extra", t, func() { - Convey("Extra()", func() { - data := map[string]interface{}{ - "redis": map[string]interface{}{ - "host": "redis", - "port": 6379, + data := map[string]interface{}{ + "redis": map[string]interface{}{ + "host": "redis", + "port": 6379, + }, + } + + assert.Nil(t, Extra(nil), "it should return nil if the data is nil") + + o := Extra(data) + assert.NotNil(t, o, "it should return a non-nil option when the data is not nil") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "extra", o.Class(), "it should use the right option class") + + if assert.Implements(t, (*MergeableOption)(nil), o, "it should implement the MergeableOption interface") { + om := o.(MergeableOption) + + t.Run("Merge()", func(t *testing.T) { + data2 := map[string]interface{}{ + "cache": map[string]interface{}{ + "key": "user.127.profile", + "hit": false, }, } - Convey("Should return nil if the data is nil", func() { - So(Extra(nil), ShouldBeNil) - }) + o2 := Extra(data2) - Convey("Should return an Option", func() { - So(Extra(data), ShouldImplement, (*Option)(nil)) - }) + assert.Equal(t, o, om.Merge(&testOption{}), "it should replace the old option if it is not recognized") - Convey("Should use the correct Class()", func() { - So(Extra(data).Class(), ShouldEqual, "extra") - }) + oo := om.Merge(o2) + assert.NotNil(t, oo, "it should return a non-nil merge result") + assert.NotEqual(t, o, oo, "it should not re-purpose the first option") + assert.NotEqual(t, o2, oo, "it should not re-purpose the second option") - Convey("Should implement Merge()", func() { - So(Extra(data), ShouldImplement, (*MergeableOption)(nil)) - }) - }) + eo, ok := oo.(*extraOption) + assert.True(t, ok, "it should actually be an *extraOption") + + assert.Contains(t, eo.extra, "redis", "it should contain the key from the first option") + assert.Contains(t, eo.extra, "cache", "it should contain the key from the second option") - Convey("Merge()", func() { - data1 := map[string]interface{}{ + data2 = map[string]interface{}{ "redis": map[string]interface{}{ - "host": "redis", + "host": "redis-dev", "port": 6379, }, } - e1 := Extra(data1) - So(e1, ShouldNotBeNil) - - e1m, ok := e1.(MergeableOption) - So(ok, ShouldBeTrue) - - Convey("Should overwrite if it doesn't recognize the old option", func() { - So(e1m.Merge(&testOption{}), ShouldEqual, e1) - }) - - Convey("Should merge multiple extra entries", func() { - data2 := map[string]interface{}{ - "cache": map[string]interface{}{ - "key": "user.127.profile", - "hit": false, - }, - } - e2 := Extra(data2) - So(e2, ShouldNotBeNil) - - em := e1m.Merge(e2) - So(em, ShouldNotBeNil) - So(em, ShouldNotEqual, e1) - So(em, ShouldNotEqual, e2) - - emm, ok := em.(*extraOption) - So(ok, ShouldBeTrue) - So(emm.extra, ShouldContainKey, "cache") - So(emm.extra, ShouldContainKey, "redis") - }) - - Convey("Should overwrite old entries with new ones", func() { - data2 := map[string]interface{}{ - "redis": map[string]interface{}{ - "host": "redis-dev", - "port": 6379, - }, - } - e2 := Extra(data2) - So(e2, ShouldNotBeNil) - - em := e1m.Merge(e2) - So(em, ShouldNotBeNil) - So(em, ShouldNotEqual, e1) - So(em, ShouldNotEqual, e2) - - emm, ok := em.(*extraOption) - So(ok, ShouldBeTrue) - So(emm.extra, ShouldContainKey, "redis") - So(emm.extra["redis"], ShouldResemble, map[string]interface{}{ - "host": "redis", - "port": 6379, - }) - }) - }) - Convey("MarshalJSON", func() { - Convey("Should marshal the fields correctly", func() { - data := map[string]interface{}{ - "redis": map[string]interface{}{ - "host": "redis", - // Float mode required since we aren't deserializing into an int - "port": 6379., - }, - } - - serialized := testOptionsSerialize(Extra(data)) - So(serialized, ShouldNotBeNil) - So(serialized, ShouldHaveSameTypeAs, data) - So(serialized, ShouldResemble, data) - }) + o2 = Extra(data2) + assert.NotNil(t, o2, "it should not be nil") + oo = om.Merge(o2) + assert.NotNil(t, oo, "it should return a non-nil merge result") + + eo, ok = oo.(*extraOption) + assert.True(t, ok, "it should actually be an *extraOption") + + assert.Contains(t, eo.extra, "redis", "it should contain the key") + assert.Equal(t, data, eo.extra, "it should use the new option's data") }) + } + + t.Run("MarshalJSON()", func(t *testing.T) { + data := map[string]interface{}{ + "redis": map[string]interface{}{ + "host": "redis", + // Float mode required since we aren't deserializing into an int + "port": 6379., + }, + } + + serialized := testOptionsSerialize(t, Extra(data)) + assert.Equal(t, data, serialized, "it should serialize to the data") }) } diff --git a/fingerprint_test.go b/fingerprint_test.go index a412a97..e6538b2 100644 --- a/fingerprint_test.go +++ b/fingerprint_test.go @@ -3,7 +3,7 @@ package sentry import ( "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleFingerprint() { @@ -19,19 +19,12 @@ func ExampleFingerprint() { } func TestFingerprint(t *testing.T) { - Convey("Fingerprint", t, func() { - Convey("Fingerprint()", func() { - Convey("Should return an Option", func() { - So(Fingerprint("test"), ShouldImplement, (*Option)(nil)) - }) - }) + o := Fingerprint("test") + assert.NotNil(t, o, "it should not return a nil option") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "fingerprint", o.Class(), "it should use the right option class") - Convey("Should use the correct class", func() { - So(Fingerprint("test").Class(), ShouldEqual, "fingerprint") - }) - - Convey("MarshalJSON", func() { - So(testOptionsSerialize(Fingerprint("test")), ShouldResemble, []interface{}{"test"}) - }) + t.Run("MarshalJSON()", func (t *testing.T) { + assert.Equal(t, []interface{}{"test"}, testOptionsSerialize(t, o), "it should serialize as a list of fingerprint keys") }) } diff --git a/go.mod b/go.mod index e87a41d..c8361ec 100644 --- a/go.mod +++ b/go.mod @@ -2,14 +2,10 @@ module github.com/SierraSoftworks/sentry-go require ( github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448 - github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/matishsiao/goInfo v0.0.0-20170803142006-617e6440957e github.com/pkg/errors v0.8.1 - github.com/sirupsen/logrus v1.3.0 - github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac // indirect - github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a - github.com/stretchr/testify v1.3.0 // indirect - golang.org/x/sys v0.0.0-20190220154126-629670e5acc5 // indirect + github.com/stretchr/testify v1.4.0 ) go 1.13 diff --git a/go.sum b/go.sum index 25d7359..3997b00 100644 --- a/go.sum +++ b/go.sum @@ -3,47 +3,16 @@ github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEex 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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= -github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= -github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/matishsiao/goInfo v0.0.0-20170803142006-617e6440957e h1:Y+GY+bv5vf1gssphFsGiq6R8qdHxnpDZvYljFnXfhD8= github.com/matishsiao/goInfo v0.0.0-20170803142006-617e6440957e/go.mod h1:yLZrFIhv+Z20hxHvcZpEyKVQp9HMsOJkXAxx7yDqtvg= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME= -github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac h1:wbW+Bybf9pXxnCFAOWZTqkRjAc7rAIwo2e1ArUhiHxg= -github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w= -github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2 h1:NwxKRvbkH5MsNkvOtPZi3/3kmI8CAzs3mtv+GLQMkNo= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190220154126-629670e5acc5 h1:3Nsfe5Xa1wTt01QxlAFIY5j9ycDtS+d7mhvI8ZY5bn0= -golang.org/x/sys v0.0.0-20190220154126-629670e5acc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/httpTransport.go b/httpTransport.go index ba68ac6..f69de13 100644 --- a/httpTransport.go +++ b/httpTransport.go @@ -9,9 +9,9 @@ import ( "fmt" "io" "io/ioutil" + "log" "net/http" - log "github.com/sirupsen/logrus" "github.com/certifi/gocertifi" "github.com/pkg/errors" ) @@ -33,7 +33,8 @@ func newHTTPTransport() Transport { rootCAs, err := gocertifi.CACerts() if err != nil { - log.WithError(errors.Wrap(err, ErrMissingRootTLSCerts.Error())).Error(ErrMissingRootTLSCerts.Error()) + + log.Println(ErrMissingRootTLSCerts.Error()) return t } @@ -71,11 +72,6 @@ func (t *httpTransport) Send(dsn string, packet Packet) error { req.Header.Set("Content-Type", contentType) req.Header.Set("User-Agent", fmt.Sprintf("sentry-go %s (Sierra Softworks; github.com/SierraSoftworks/sentry-go)", version)) - log.WithFields(log.Fields{ - "method": req.Method, - "url": req.URL.String(), - }).Debug("sentry: Making request to send event") - res, err := t.client.Do(req) if err != nil { return errors.Wrap(err, "failed to submit request") diff --git a/httpTransport_test.go b/httpTransport_test.go index e3f5ddb..4bd7edb 100644 --- a/httpTransport_test.go +++ b/httpTransport_test.go @@ -13,34 +13,28 @@ import ( "strings" "testing" - log "github.com/sirupsen/logrus" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestHTTPTransport(t *testing.T) { - deserializePacket := func(dataType string, data io.Reader) (interface{}, error) { + deserializePacket := func(t *testing.T, dataType string, data io.Reader) interface{} { var out interface{} if strings.Contains(dataType, "application/json") { - if err := json.NewDecoder(data).Decode(&out); err != nil { - return nil, err - } + require.Nil(t, json.NewDecoder(data).Decode(&out), "there should be no problems deserializing the packet") } else if strings.Contains(dataType, "application/octet-stream") { b64 := base64.NewDecoder(base64.StdEncoding, data) deflate, err := zlib.NewReader(b64) defer deflate.Close() - if err != nil { - return nil, err - } - if err := json.NewDecoder(deflate).Decode(&out); err != nil { - return nil, err - } + require.Nil(t, err, "there should be no errors creating the zlib deflator") + require.Nil(t, json.NewDecoder(deflate).Decode(&out), "there should be no problems deserializing the packet") } else { - return nil, fmt.Errorf("unknown datatype for packet: %s", dataType) + t.Fatalf("unknown datatype for packet: %s", dataType) } - return out, nil + return out } longMessage := func(minLength int) Option { @@ -52,171 +46,163 @@ func TestHTTPTransport(t *testing.T) { return Message(msg) } - Convey("HTTPTransport", t, func() { - t := newHTTPTransport() - So(t, ShouldNotBeNil) - - ht, ok := t.(*httpTransport) - So(ok, ShouldBeTrue) - - Convey("newHTTPTransport", func() { - So(ht.client, ShouldNotEqual, http.DefaultClient) - }) - - Convey("Send()", func(c C) { - p := NewPacket() - - received := false - statusCode := 200 + tr := newHTTPTransport() + require.NotNil(t, tr, "the transport should not be nil") - mux := http.NewServeMux() - mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { - received = true - res.WriteHeader(statusCode) - res.Write([]byte("No data")) + ht, ok := tr.(*httpTransport) + require.True(t, ok, "it should actually be a *httpTransport") - c.So(req.Method, ShouldEqual, "POST") - c.So(req.RequestURI, ShouldEqual, "/api/1/store/") - c.So(req.Header.Get("X-Sentry-Auth"), ShouldEqual, "Sentry sentry_version=4, sentry_key=user, sentry_secret=pass") + t.Run("Send()", func(t *testing.T) { + p := NewPacket() + require.NotNil(t, p, "the packet should not be nil") - expectedData, err := testSerializePacket(p) - c.So(err, ShouldBeNil) + received := false + statusCode := 200 - data, err := deserializePacket(req.Header.Get("Content-Type"), req.Body) - c.So(err, ShouldBeNil) - c.So(data, ShouldNotBeNil) - c.So(data, ShouldResemble, expectedData) - }) + mux := http.NewServeMux() + require.NotNil(t, mux, "the http mux should not be nil") + mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { + received = true + res.WriteHeader(statusCode) + res.Write([]byte("No data")) - ts := httptest.NewServer(mux) - defer ts.Close() + assert.Equal(t, "POST", req.Method, "the request should use HTTP POST") + assert.Equal(t, "/api/1/store/", req.RequestURI, "the request should use the right API endpoint") - uri, err := url.Parse(ts.URL) - So(err, ShouldBeNil) - uri.User = url.UserPassword("user", "pass") - uri.Path = "/1" + assert.Contains(t, []string{ + "Sentry sentry_version=4, sentry_key=key, sentry_secret=secret", + "Sentry sentry_version=4, sentry_key=key", + }, req.Header.Get("X-Sentry-Auth"), "it should use the right auth header") - dsn := uri.String() + expectedData := testSerializePacket(t, p) - Convey("With a small packet", func() { - So(t.Send(dsn, p), ShouldBeNil) - So(received, ShouldBeTrue) - }) + data := deserializePacket(t, req.Header.Get("Content-Type"), req.Body) - Convey("With a large packet", func() { - p.SetOptions(longMessage(1000)) - So(t.Send(dsn, p), ShouldBeNil) - So(received, ShouldBeTrue) - }) - - Convey("Without a DSN", func() { - So(t.Send("", p), ShouldBeNil) - So(received, ShouldBeFalse) - }) - - Convey("With an invalid DSN URL", func() { - err := t.Send(":", p) - So(err, ShouldNotBeNil) - So(ErrBadURL.IsInstance(err), ShouldBeTrue) - }) - - Convey("With a missing public key", func() { - err := t.Send("https://example.com/sentry/1", p) - So(err, ShouldNotBeNil) - So(ErrMissingPublicKey.IsInstance(err), ShouldBeTrue) - }) - - Convey("With a missing private key", func() { - err := t.Send("https://key@example.com/sentry/1", p) - So(err, ShouldNotBeNil) - So(err.Error(), ShouldEqual, "got http status 404, expected 200") - }) - - Convey("When it cannot connect to the server", func() { - err := t.Send("https://key:secret@invalid_domain.not_a_tld/sentry/1", p) - So(err, ShouldNotBeNil) - So(err.Error(), ShouldStartWith, "failed to submit request: ") - }) - - Convey("When an HTTP error is encountered", func() { - statusCode = 403 - err := t.Send(dsn, p) - So(err, ShouldNotBeNil) - So(err.Error(), ShouldEqual, "got http status 403, expected 200") - }) + assert.Equal(t, expectedData, data, "the data should match what we expected") }) - Convey("serializePacket()", func() { - p := NewPacket() + ts := httptest.NewServer(mux) + defer ts.Close() - Convey("Short Packet", func() { - data, dataType, err := ht.serializePacket(p) - So(err, ShouldBeNil) - So(data, ShouldNotBeNil) - So(dataType, ShouldContainSubstring, "application/json") + makeDSN := func(publicKey, privateKey string) string { + uri, err := url.Parse(ts.URL) + require.Nil(t, err, "we should not fail to parse the URI") - pd, err := deserializePacket(dataType, data) - So(err, ShouldBeNil) + if publicKey != "" { + uri.User = url.UserPassword(publicKey, privateKey) + } - ped, err := testSerializePacket(p) - So(err, ShouldBeNil) - So(pd, ShouldResemble, ped) - }) + uri.Path = "/1" - Convey("Long Packet", func() { - p.SetOptions(longMessage(10000)) - data, dataType, err := ht.serializePacket(p) - So(err, ShouldBeNil) - So(data, ShouldNotBeNil) - So(dataType, ShouldContainSubstring, "application/octet-stream") + return uri.String() + } - pd, err := deserializePacket(dataType, data) - So(err, ShouldBeNil) + cases := []struct { + Name string + Packet Packet + DSN string + StatusCode int + Error error + Received bool + }{ + {"Short Packet", NewPacket(), makeDSN("key", "secret"), 200, nil, true}, + {"Long Packet", NewPacket().SetOptions(longMessage(10000)), makeDSN("key", "secret"), 200, nil, true}, + {"No DSN", NewPacket(), "", 200, nil, false}, + {"Invalid DSN URL", NewPacket(), ":", 400, ErrBadURL, false}, + {"Missing Public Key", NewPacket(), makeDSN("", ""), 401, ErrMissingPublicKey, false}, + {"Invalid Server", NewPacket(), "https://key:secret@invalid_domain.not_a_tld/sentry/1", 404, ErrType("failed to submit request"), false}, + {"Missing Private Key with Required Key", NewPacket(), makeDSN("key", ""), 401, fmt.Errorf("got http status 401, expected 200"), true}, + {"Missing Private Key", NewPacket(), makeDSN("key", ""), 200, nil, true}, + } - ped, err := testSerializePacket(p) - So(err, ShouldBeNil) - So(pd, ShouldResemble, ped) + for _, tc := range cases { + tc := tc + + t.Run(tc.Name, func(t *testing.T) { + received = false + statusCode = tc.StatusCode + p = tc.Packet + + err := tr.Send(tc.DSN, tc.Packet) + if tc.Error == nil { + assert.Nil(t, err, "it should not fail to send the packet") + } else if errType, ok := tc.Error.(ErrType); ok { + assert.True(t, errType.IsInstance(err), "it should return the right error") + } else { + assert.EqualError(t, err, tc.Error.Error(), "it should return the right error") + } + + if tc.Received { + assert.True(t, received, "the server should have received the packet") + } else { + assert.False(t, received, "the server should not have received the packet") + } }) - }) + } + }) - Convey("parseDSN()", func() { - Convey("With an empty DSN", func() { - url, authheader, err := ht.parseDSN("") - So(err, ShouldBeNil) - So(url, ShouldEqual, "") - So(authheader, ShouldEqual, "") - }) + t.Run("serializePacket()", func(t *testing.T) { + cases := []struct { + Name string + Packet Packet + DataType string + }{ + {"Short Packet", NewPacket().SetOptions(Message("short packet")), "application/json; charset=utf8"}, + {"Long Packet", NewPacket().SetOptions(longMessage(10000)), "application/octet-stream"}, + } - Convey("With an invalid DSN", func() { - url, authheader, err := ht.parseDSN("@") - So(err, ShouldNotBeNil) - So(url, ShouldEqual, "") - So(authheader, ShouldEqual, "") - }) + for _, tc := range cases { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + data, dataType, err := ht.serializePacket(tc.Packet) + assert.Nil(t, err, "there should be no error serializing the packet") + assert.Equal(t, tc.DataType, dataType, "the request datatype should be %s", tc.DataType) + assert.NotNil(t, data, "the request data should not be nil") - Convey("With a valid DSN", func() { - url, authHeader, err := ht.parseDSN("https://user:pass@example.com/sentry/1") - So(err, ShouldBeNil) - So(url, ShouldEqual, "https://example.com/sentry/api/1/store/") - So(authHeader, ShouldEqual, "Sentry sentry_version=4, sentry_key=user, sentry_secret=pass") + assert.Equal(t, testSerializePacket(t, tc.Packet), deserializePacket(t, dataType, data), "the serialized packet should match what we expected") }) - }) - - // If you set $SENTRY_DSN you can send events to a live Sentry instance - // to confirm that this library functions correctly. - if liveTestDSN := os.Getenv("SENTRY_DSN"); liveTestDSN != "" { - Convey("Live Test", func() { - log.SetLevel(log.DebugLevel) - defer log.SetLevel(log.InfoLevel) + } + }) - p := NewPacket().SetOptions( - Message("Ran Live Test"), - Release(version), - Level(Debug), - ) + t.Run("parseDSN()", func(t *testing.T) { + cases := []struct { + Name string + DSN string + URL string + AuthHeader string + Error error + }{ + {"Empty DSN", "", "", "", nil}, + {"Invalid DSN", "@", "", "", fmt.Errorf("sentry: missing public key: missing URL user")}, + {"Full DSN", "https://user:pass@example.com/sentry/1", "https://example.com/sentry/api/1/store/", "Sentry sentry_version=4, sentry_key=user, sentry_secret=pass", nil}, + } - So(t.Send(liveTestDSN, p), ShouldBeNil) + for _, tc := range cases { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + url, authHeader, err := ht.parseDSN(tc.DSN) + if tc.Error != nil { + assert.EqualError(t, err, tc.Error.Error(), "there should be an error with the right message") + } else { + assert.Nil(t, err, "there should be no error") + } + assert.Equal(t, tc.URL, url, "the parsed URL should be correct") + assert.Equal(t, tc.AuthHeader, authHeader, "the parsed auth header should be correct") }) } }) + + // If you set $SENTRY_DSN you can send events to a live Sentry instance + // to confirm that this library functions correctly. + if liveTestDSN := os.Getenv("SENTRY_DSN"); liveTestDSN != "" { + t.Run("Live Test", func(t *testing.T) { + p := NewPacket().SetOptions( + Message("Ran Live Test"), + Release(version), + Level(Debug), + ) + + assert.Nil(t, tr.Send(liveTestDSN, p), "it should not fail to send the packet") + }) + } } diff --git a/http_test.go b/http_test.go index 4e62175..1366d73 100644 --- a/http_test.go +++ b/http_test.go @@ -3,7 +3,7 @@ package sentry import ( "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleHTTP() { @@ -45,30 +45,30 @@ func ExampleHTTP() { } func TestHTTP(t *testing.T) { - Convey("HTTP", t, func() { - r := &HTTPRequestInfo{ - URL: "http://example.com/my.url", - Method: "GET", - Query: "q=test", + r := &HTTPRequestInfo{ + URL: "http://example.com/my.url", + Method: "GET", + Query: "q=test", + + Cookies: "testing=1", + } + + assert.Equal(t, "request", r.Class(), "request info should use the correct option class") - Cookies: "testing=1", - } + assert.Nil(t, HTTP(nil), "it should return nil if it receives a nil request") - Convey("HTTP()", func() { - Convey("Should return an Option", func() { - So(HTTP(r), ShouldImplement, (*Option)(nil)) - }) + o := HTTP(r) + assert.NotNil(t, o, "it should not return a nil option") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") - Convey("Should return nil if the data is nil", func() { - So(HTTP(nil), ShouldBeNil) - }) - }) + assert.Equal(t, "request", o.Class(), "it should use the correct option class") - Convey("HTTPRequestInfo", func() { - Convey("Should use the correct Class()", func() { - So(r.Class(), ShouldEqual, "request") - So(HTTP(r).Class(), ShouldEqual, "request") - }) - }) + t.Run("MarshalJSON()", func(t *testing.T) { + assert.Equal(t, map[string]interface{}{ + "url": "http://example.com/my.url", + "method": "GET", + "query_string": "q=test", + "cookies": "testing=1", + }, testOptionsSerialize(t, o), "it should serialize the request info correctly") }) } diff --git a/httprequest.go b/httprequest.go index c06fc7c..ad95ea4 100644 --- a/httprequest.go +++ b/httprequest.go @@ -35,6 +35,8 @@ func HTTPRequest(req *http.Request) HTTPRequestOption { } } +const sanitizationString = "********" + type httpRequestOption struct { request *http.Request withCookies bool @@ -115,10 +117,8 @@ func (h *httpRequestOption) buildData() *HTTPRequestInfo { v = ps[1] } - for _, keyword := range h.sanitize { - if strings.Contains(k, keyword) { - v = "********" - } + if shouldSanitize(k, h.sanitize) { + v = sanitizationString } p.Env[k] = v @@ -130,22 +130,29 @@ func (h *httpRequestOption) buildData() *HTTPRequestInfo { for k, v := range h.request.Header { p.Headers[k] = strings.Join(v, ",") - for _, keyword := range h.sanitize { - if strings.Contains(k, keyword) { - p.Headers[k] = "********" - break - } + if shouldSanitize(k, h.sanitize) { + p.Headers[k] = sanitizationString } } } return p } +func shouldSanitize(s string, fields []string) bool { + for _, keyword := range fields { + if strings.Contains(strings.ToLower(s), strings.ToLower(keyword)) { + return true + } + } + + return false +} + func sanitizeQuery(query url.Values, fields []string) url.Values { for field := range query { for _, keyword := range fields { - if strings.Contains(field, keyword) { - query[field] = []string{"********"} + if strings.Contains(strings.ToLower(field), strings.ToLower(keyword)) { + query[field] = []string{sanitizationString} break } } diff --git a/httprequest_test.go b/httprequest_test.go index 4386a2e..77f0f68 100644 --- a/httprequest_test.go +++ b/httprequest_test.go @@ -3,9 +3,11 @@ package sentry import ( "net/http" "net/url" + "os" "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func ExampleHTTPRequest() { @@ -33,151 +35,145 @@ func ExampleHTTPRequest() { } func TestHTTPRequest(t *testing.T) { - Convey("HTTPRequest", t, func() { - r, err := http.NewRequest("GET", "https://example.com/test?testing=1&password=test", nil) - So(err, ShouldBeNil) - - r.RemoteAddr = "127.0.0.1:12835" - r.Header.Set("Host", "example.com") - r.Header.Set("X-Forwarded-Proto", "https") - r.Header.Set("Cookie", "testing=1") - r.Header.Set("X-Testing", "1") - - Convey("HTTPRequest()", func() { - Convey("Should return an Option", func() { - So(HTTPRequest(r), ShouldImplement, (*Option)(nil)) - }) - - Convey("Should not return nil if request is nil", func() { - So(HTTPRequest(nil), ShouldNotBeNil) - }) - }) - - Convey("Should use the correct Class()", func() { - So(HTTPRequest(r).Class(), ShouldEqual, "request") - }) - - Convey("Omit()", func() { - Convey("Should return false with a valid request", func() { - So(HTTPRequest(r).(*httpRequestOption).Omit(), ShouldBeFalse) - }) - - Convey("Should return true if no request was provided", func() { - So(HTTPRequest(nil).(*httpRequestOption).Omit(), ShouldBeTrue) - }) - }) - - Convey("buildData()", func() { - Convey("With the default config", func() { - opt := HTTPRequest(r) - hr, ok := opt.(*httpRequestOption) - So(ok, ShouldBeTrue) - - d := hr.buildData() - So(d, ShouldNotBeNil) - - So(d.Method, ShouldEqual, "GET") - So(d.URL, ShouldEqual, "https://example.com/test") - So(d.Query, ShouldEqual, url.Values{ - "testing": {"1"}, - "password": {"********"}, - }.Encode()) - - So(d.Data, ShouldBeNil) - So(d.Headers, ShouldResemble, map[string]string{}) - So(d.Env, ShouldResemble, map[string]string{}) - So(d.Cookies, ShouldEqual, "") - }) - - Convey("With cookies enabled", func() { - opt := HTTPRequest(r).WithCookies() - hr, ok := opt.(*httpRequestOption) - So(ok, ShouldBeTrue) - - d := hr.buildData() - So(d, ShouldNotBeNil) - - So(d.Method, ShouldEqual, "GET") - So(d.URL, ShouldEqual, "https://example.com/test") - So(d.Query, ShouldEqual, url.Values{ - "testing": {"1"}, - "password": {"********"}, - }.Encode()) - - So(d.Data, ShouldBeNil) - So(d.Headers, ShouldResemble, map[string]string{}) - So(d.Env, ShouldResemble, map[string]string{}) - So(d.Cookies, ShouldEqual, "testing=1") - }) - - Convey("With headers enabled", func() { - opt := HTTPRequest(r).WithHeaders() - hr, ok := opt.(*httpRequestOption) - So(ok, ShouldBeTrue) - - d := hr.buildData() - So(d, ShouldNotBeNil) - - So(d.Method, ShouldEqual, "GET") - So(d.URL, ShouldEqual, "https://example.com/test") - So(d.Query, ShouldEqual, url.Values{ - "testing": {"1"}, - "password": {"********"}, - }.Encode()) - - So(d.Data, ShouldBeNil) - So(d.Headers, ShouldResemble, map[string]string{ - "Host": "example.com", - "Cookie": "testing=1", - "X-Testing": "1", - "X-Forwarded-Proto": "https", - }) - So(d.Env, ShouldResemble, map[string]string{}) - So(d.Cookies, ShouldEqual, "") - }) - - Convey("With env enabled", func() { - opt := HTTPRequest(r).WithEnv() - hr, ok := opt.(*httpRequestOption) - So(ok, ShouldBeTrue) - - d := hr.buildData() - So(d, ShouldNotBeNil) - - So(d.Method, ShouldEqual, "GET") - So(d.URL, ShouldEqual, "https://example.com/test") - So(d.Query, ShouldEqual, url.Values{ - "testing": {"1"}, - "password": {"********"}, - }.Encode()) - - So(d.Data, ShouldBeNil) - So(d.Headers, ShouldResemble, map[string]string{}) - So(d.Env["REMOTE_ADDR"], ShouldEqual, "127.0.0.1") - So(d.Env["REMOTE_PORT"], ShouldEqual, "12835") - So(d.Cookies, ShouldEqual, "") - }) - - Convey("With data provided", func() { - opt := HTTPRequest(r).WithData("testing") - hr, ok := opt.(*httpRequestOption) - So(ok, ShouldBeTrue) - - d := hr.buildData() - So(d, ShouldNotBeNil) - - So(d.Method, ShouldEqual, "GET") - So(d.URL, ShouldEqual, "https://example.com/test") - So(d.Query, ShouldEqual, url.Values{ - "testing": {"1"}, - "password": {"********"}, - }.Encode()) - - So(d.Data, ShouldEqual, "testing") - So(d.Headers, ShouldResemble, map[string]string{}) - So(d.Env, ShouldResemble, map[string]string{}) - So(d.Cookies, ShouldEqual, "") - }) + r, err := http.NewRequest("GET", "https://example.com/test?testing=1&password=test", nil) + assert.Nil(t, err, "should be able to create an HTTP request object") + + r.RemoteAddr = "127.0.0.1:12835" + r.Header.Set("Host", "example.com") + r.Header.Set("X-Forwarded-Proto", "https") + r.Header.Set("Cookie", "testing=1") + r.Header.Set("X-Testing", "1") + r.Header.Set("X-API-Key", "secret") + + assert.NotNil(t, HTTPRequest(nil), "it should not return nil if no request is provided") + + o := HTTPRequest(r) + assert.NotNil(t, o, "should not return a nil option") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "request", o.Class(), "it should use the right option class") + + if assert.Implements(t, (*OmitableOption)(nil), o, "it should implement the OmitableOption interface") { + assert.False(t, o.(OmitableOption).Omit(), "it should return false if there is a request") + assert.True(t, HTTPRequest(nil).(OmitableOption).Omit(), "it should return true if there is no request") + } + + tm := "GET" + tu := "https://example.com/test" + tq := url.Values{ + "testing": {"1"}, + "password": {sanitizationString}, + } + var td interface{} = nil + th := map[string]string{} + te := map[string]string{} + tc := "" + + cases := []struct { + Name string + Opt Option + Setup func() + }{ + {"Default", HTTPRequest(r), func() {}}, + {"Default.Sanitize()", HTTPRequest(r).Sanitize("testing"), func() { + tq = url.Values{ + "testing": {sanitizationString}, + "password": {sanitizationString}, + } + }}, + {"WithCookies()", HTTPRequest(r).WithCookies(), func() { + tc = "testing=1" + }}, + {"WithHeaders()", HTTPRequest(r).WithHeaders(), func() { + th = map[string]string{ + "Host": "example.com", + "Cookie": "testing=1", + "X-Testing": "1", + "X-Forwarded-Proto": "https", + "X-Api-Key": "secret", + } + }}, + {"WithHeaders().Sanitize()", HTTPRequest(r).WithHeaders().Sanitize("key"), func() { + th = map[string]string{ + "Host": "example.com", + "Cookie": "testing=1", + "X-Testing": "1", + "X-Forwarded-Proto": "https", + "X-Api-Key": sanitizationString, + } + }}, + {"WithEnv()", HTTPRequest(r).WithEnv(), func() { + te = map[string]string{ + "REMOTE_ADDR": "127.0.0.1", + "REMOTE_PORT": "12835", + } + }}, + {"WithEnv().Sanitize()", HTTPRequest(r).WithEnv().Sanitize("secret"), func() { + os.Setenv("SECRET", "secret") + + te = map[string]string{ + "REMOTE_ADDR": "127.0.0.1", + "REMOTE_PORT": "12835", + "SECRET": "********", + } + }}, + {"WithData()", HTTPRequest(r).WithData("testing"), func() { + td = "testing" + }}, + } + + for _, testCase := range cases { + testCase := testCase + t.Run(testCase.Name, func(t *testing.T) { + tq = url.Values{ + "testing": {"1"}, + "password": {sanitizationString}, + } + td = nil + th = map[string]string{} + te = map[string]string{} + tc = "" + + testCase.Setup() + + hr, ok := testCase.Opt.(*httpRequestOption) + assert.True(t, ok, "the option should actually be a *httpRequestOption") + + d := hr.buildData() + assert.NotNil(t, d, "the built data should not be nil") + + assert.Equal(t, tm, d.Method, "the method should be correct") + assert.Equal(t, tu, d.URL, "the url should be correct") + assert.Equal(t, tq.Encode(), d.Query, "the query should be correct") + assert.Equal(t, td, d.Data, "the data should be correct") + assert.Equal(t, th, d.Headers, "the headers should be correct") + + for k, v := range te { + if assert.Contains(t, d.Env, k, "the environment should include the %s entry", k) { + assert.Equal(t, v, d.Env[k], "the value of the %s environment variable should be correct", k) + } + } + + assert.Equal(t, tc, d.Cookies, "the cookies should be correct") }) + } + + t.Run("MarshalJSON()", func(t *testing.T) { + o := HTTPRequest(r).WithHeaders().WithCookies().WithData("test").Sanitize("key", "secret") + + require.NotNil(t, o, "the option should not be nil") + assert.Equal(t, map[string]interface{}{ + "cookies": "testing=1", + "data": "test", + "headers": map[string]interface{}{ + "Cookie": "testing=1", + "Host": "example.com", + "X-Api-Key": "********", + "X-Forwarded-Proto": "https", + "X-Testing": "1", + }, + "method": "GET", + "query_string": "password=%2A%2A%2A%2A%2A%2A%2A%2A&testing=1", + "url": "https://example.com/test", + }, testOptionsSerialize(t, o), "the request option should be serialized correctly") }) } diff --git a/logger_test.go b/logger_test.go index 01e9069..65d2b20 100644 --- a/logger_test.go +++ b/logger_test.go @@ -1,10 +1,9 @@ package sentry import ( - "encoding/json" "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleLogger() { @@ -20,26 +19,14 @@ func ExampleLogger() { } func TestLogger(t *testing.T) { - Convey("Logger", t, func() { - Convey("Should register itself with the default providers", func() { - opt := testGetOptionsProvider(Logger("")) - So(opt, ShouldNotBeNil) - }) + assert.NotNil(t, testGetOptionsProvider(t, Logger("")), "it should be registered as a default option") - Convey("Logger()", func() { - Convey("Should use the correct Class()", func() { - So(Logger("test").Class(), ShouldEqual, "logger") - }) + o := Logger("test") + assert.NotNil(t, o, "should not return a nil option") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "logger", o.Class(), "it should use the right option class") - Convey("MarshalJSON", func() { - Convey("Should marshal to a string", func() { - Convey("Should marshal to a string", func() { - b, err := json.Marshal(Logger("test")) - So(err, ShouldBeNil) - So(string(b), ShouldEqual, `"test"`) - }) - }) - }) - }) + t.Run("MarshalJSON()", func(t *testing.T) { + assert.Equal(t, "test", testOptionsSerialize(t, o), "it should serialize to a string") }) } diff --git a/message_test.go b/message_test.go index e988fa7..f7044fe 100644 --- a/message_test.go +++ b/message_test.go @@ -3,7 +3,7 @@ package sentry import ( "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleMessage() { @@ -22,37 +22,23 @@ func ExampleMessage() { } func TestMessage(t *testing.T) { - Convey("Message", t, func() { - Convey("Message()", func() { - Convey("Should return an Option", func() { - So(Message("test"), ShouldImplement, (*Option)(nil)) - }) - - Convey("With just a message string", func() { - m := Message("test") - So(m, ShouldNotBeNil) - - mi, ok := m.(*messageOption) - So(ok, ShouldBeTrue) - - So(mi.Message, ShouldEqual, "test") - }) - - Convey("With a formatted message", func() { - m := Message("this is a %s", "test") - So(m, ShouldNotBeNil) - - mi, ok := m.(*messageOption) - So(ok, ShouldBeTrue) - - So(mi.Message, ShouldEqual, "this is a %s") - So(mi.Params, ShouldResemble, []interface{}{"test"}) - So(mi.Formatted, ShouldEqual, "this is a test") - }) - }) - - Convey("Should use the correct Class()", func() { - So(Message("test").Class(), ShouldEqual, "sentry.interfaces.Message") - }) + o := Message("test") + assert.NotNil(t, o, "should not return a nil option") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "sentry.interfaces.Message", o.Class(), "it should use the right option class") + + t.Run("MarshalJSON()", func(t *testing.T) { + assert.Equal(t, map[string]interface{}{"message":"test"}, testOptionsSerialize(t, o), "it should serialize to an object") + }) + + t.Run("parameters", func(t *testing.T) { + o := Message("this is a %s", "test") + assert.NotNil(t, o, "should not return a nil option") + + mi, ok := o.(*messageOption) + assert.True(t, ok, "it should actually be a *messageOption") + assert.Equal(t, "this is a %s", mi.Message, "it should use the right message") + assert.Equal(t, []interface{}{"test"}, mi.Params, "it should have the correct parameters") + assert.Equal(t, "this is a test", mi.Formatted, "it should format the message when requested") }) } diff --git a/modules_go1.12_test.go b/modules_go1.12_test.go index c0911eb..8d2817f 100644 --- a/modules_go1.12_test.go +++ b/modules_go1.12_test.go @@ -6,18 +6,12 @@ import ( "runtime/debug" "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func TestModulesWithGomod(t *testing.T) { - Convey("Modules", t, func() { - _, ok := debug.ReadBuildInfo() - - if ok { - Convey("Should register itself with the default providers", func() { - opt := testGetOptionsProvider(Modules(map[string]string{"test": "correct"})) - So(opt, ShouldNotBeNil) - }) - } - }) + _, ok := debug.ReadBuildInfo() + if ok { + assert.NotNil(t, testGetOptionsProvider(t, Modules(map[string]string{"test": "correct"})), "it should be registered as a default provider") + } } diff --git a/modules_test.go b/modules_test.go index 9180c53..6e28808 100644 --- a/modules_test.go +++ b/modules_test.go @@ -3,7 +3,8 @@ package sentry import ( "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func ExampleModules() { @@ -12,110 +13,82 @@ func ExampleModules() { // client Modules(map[string]string{ "redis": "v1", - "mgo": "v2", + "mgo": "v2", }), ) cl.Capture( // And override or expand on them when sending an event Modules(map[string]string{ - "redis": "v2", + "redis": "v2", "sentry-go": "v1", }), ) } func TestModules(t *testing.T) { - Convey("Modules", t, func() { - Convey("Modules()", func() { - data := map[string]string{ - "redis": "1.0.0", - } - - Convey("Should return nil if the data is nil", func() { - So(Modules(nil), ShouldBeNil) - }) - - Convey("Should return an Option", func() { - So(Modules(data), ShouldImplement, (*Option)(nil)) - }) - - Convey("Should use the correct Class()", func() { - So(Modules(data).Class(), ShouldEqual, "modules") - }) + assert.Nil(t, Modules(nil), "it should return nil if the data provided is nil") - Convey("Should implement Merge()", func() { - So(Modules(data), ShouldImplement, (*MergeableOption)(nil)) - }) - }) + data := map[string]string{ + "redis": "1.0.0", + } - Convey("Merge()", func() { - data1 := map[string]string{ - "redis": "1.0.0", - } - e1 := Modules(data1) - So(e1, ShouldNotBeNil) + o := Modules(data) + require.NotNil(t, o, "it should not return nil if the data is non-nil") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "modules", o.Class(), "it should use the right option class") - e1m, ok := e1.(MergeableOption) - So(ok, ShouldBeTrue) + if assert.Implements(t, (*MergeableOption)(nil), o, "it should implement the MergeableOption interface") { + t.Run("Merge()", func(t *testing.T) { + om := o.(MergeableOption) - Convey("Should overwrite if it doesn't recognize the old option", func() { - So(e1m.Merge(&testOption{}), ShouldEqual, e1) - }) + assert.Equal(t, o, om.Merge(&testOption{}), "it should replace the old option if it is not recognized") - Convey("Should merge multiple modules entries", func() { + t.Run("different entries", func(t *testing.T) { data2 := map[string]string{ "pgsql": "5.4.0", } + o2 := Modules(data2) + require.NotNil(t, o2, "the second module option should not be nil") - e2 := Modules(data2) - So(e2, ShouldNotBeNil) + oo := om.Merge(o2) + require.NotNil(t, oo, "it should not return nil when it merges") - em := e1m.Merge(e2) - So(em, ShouldNotBeNil) - So(em, ShouldNotEqual, e1) - So(em, ShouldNotEqual, e2) + ooi, ok := oo.(*modulesOption) + require.True(t, ok, "it should actually be a *modulesOption") - emm, ok := em.(*modulesOption) - So(ok, ShouldBeTrue) - So(emm.moduleVersions, ShouldContainKey, "redis") - So(emm.moduleVersions, ShouldContainKey, "pgsql") + if assert.Contains(t, ooi.moduleVersions, "redis", "it should contain the first key") { + assert.Equal(t, data["redis"], ooi.moduleVersions["redis"], "it should have the right value for the first key") + } + + if assert.Contains(t, ooi.moduleVersions, "pgsql", "it should contain the second key") { + assert.Equal(t, data2["pgsql"], ooi.moduleVersions["pgsql"], "it should have the right value for the second key") + } }) - Convey("Should overwrite old entries with new ones", func() { + t.Run("existing entries", func(t *testing.T) { data2 := map[string]string{ "redis": "0.8.0", } - e2 := Modules(data2) - So(e2, ShouldNotBeNil) - - em := e1m.Merge(e2) - So(em, ShouldNotBeNil) - So(em, ShouldNotEqual, e1) - So(em, ShouldNotEqual, e2) - - emm, ok := em.(*modulesOption) - So(ok, ShouldBeTrue) - So(emm.moduleVersions, ShouldContainKey, "redis") - So(emm.moduleVersions["redis"], ShouldEqual, "1.0.0") - }) - }) + o2 := Modules(data2) + require.NotNil(t, o2, "the second module option should not be nil") - Convey("MarshalJSON", func() { - Convey("Should marshal the fields correctly", func() { - data := map[string]string{ - "redis": "1.0.0", - } + oo := om.Merge(o2) + require.NotNil(t, oo, "it should not return nil when it merges") - serialized := testOptionsSerialize(Modules(data)) - So(serialized, ShouldNotBeNil) + ooi, ok := oo.(*modulesOption) + require.True(t, ok, "it should actually be a *modulesOption") - expected := map[string]interface{}{ - "redis": "1.0.0", + if assert.Contains(t, ooi.moduleVersions, "redis", "it should contain the first key") { + assert.Equal(t, data["redis"], ooi.moduleVersions["redis"], "it should have the right value for the first key") } - So(serialized, ShouldHaveSameTypeAs, expected) - So(serialized, ShouldResemble, expected) }) }) + } + + t.Run("MarshalJSON()", func(t *testing.T) { + assert.Equal(t, map[string]interface{}{ + "redis": "1.0.0", + }, testOptionsSerialize(t, o)) }) } diff --git a/options.go b/options.go index dbf5b75..dd97a52 100644 --- a/options.go +++ b/options.go @@ -24,10 +24,10 @@ type MergeableOption interface { Merge(old Option) Option } -// A FinalizableOption exposes a Finalize() method which is called by the +// A FinalizeableOption exposes a Finalize() method which is called by the // Packet builder before its value is used. This gives the option the opportunity // to perform any last-minute formatting and configuration. -type FinalizableOption interface { +type FinalizeableOption interface { Finalize() } diff --git a/options_test.go b/options_test.go index 133b0ff..762460e 100644 --- a/options_test.go +++ b/options_test.go @@ -8,8 +8,7 @@ import ( "testing" - "github.com/smartystreets/goconvey/convey" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleAddDefaultOptions() { @@ -34,43 +33,38 @@ func ExampleAddDefaultOptionProvider() { } func TestOptions(t *testing.T) { - Convey("Options", t, func() { - oldOptionsProviders := defaultOptionProviders - defaultOptionProviders = []func() Option{} - defer func() { - defaultOptionProviders = oldOptionsProviders - }() + oldOptionsProviders := defaultOptionProviders + defer func() { + defaultOptionProviders = oldOptionsProviders + }() - Convey("AddDefaultOptionProvider", func() { - So(defaultOptionProviders, ShouldBeEmpty) + id, err := NewEventID() + assert.Nil(t, err, "there should be no errors creating the ID") - id, err := NewEventID() - So(err, ShouldBeNil) - provider := func() Option { - return EventID(id) - } + t.Run("AddDefaultOptionProvider()", func(t *testing.T) { + defaultOptionProviders = []func() Option{} - AddDefaultOptionProvider(provider) - So(defaultOptionProviders, ShouldHaveLength, 1) + provider := func() Option { + return EventID(id) + } - for _, provider := range defaultOptionProviders { - So(provider(), ShouldResemble, EventID(id)) - } - }) + AddDefaultOptionProvider(provider) + assert.Len(t, defaultOptionProviders, 1, "the provider should now be present in the default options providers list") - Convey("AddDefaultOptions", func() { - So(defaultOptionProviders, ShouldBeEmpty) + for _, provider := range defaultOptionProviders { + assert.Equal(t, EventID(id), provider(), "the provider should return the right option") + } + }) - id, err := NewEventID() - So(err, ShouldBeNil) + t.Run("AddDefaultOptions()", func(t *testing.T) { + defaultOptionProviders = []func() Option{} - AddDefaultOptions(EventID(id), nil, EventID(id)) - So(defaultOptionProviders, ShouldHaveLength, 2) + AddDefaultOptions(EventID(id), nil, EventID(id)) + assert.Len(t, defaultOptionProviders, 2, "the provider should now be present in the default options providers list") - for _, provider := range defaultOptionProviders { - So(provider(), ShouldResemble, EventID(id)) - } - }) + for _, provider := range defaultOptionProviders { + assert.Equal(t, EventID(id), provider(), "the provider should return the right option") + } }) } @@ -101,15 +95,15 @@ func (o *testOmitableOption) Omit() bool { return o.omit } -type testFinalizableOption struct { +type testFinalizeableOption struct { finalized bool } -func (o *testFinalizableOption) Class() string { +func (o *testFinalizeableOption) Class() string { return "test" } -func (o *testFinalizableOption) Finalize() { +func (o *testFinalizeableOption) Finalize() { o.finalized = true } @@ -157,9 +151,9 @@ func (o *testSerializableOption) MarshalJSON() ([]byte, error) { return json.Marshal(o.data) } -func testGetOptionsProvider(sameType Option) Option { +func testGetOptionsProvider(t *testing.T, sameType Option) Option { st := reflect.TypeOf(sameType) - convey.So(st, convey.ShouldNotBeNil) + assert.NotNil(t, st, "getting the reflection type should not fail") for _, provider := range defaultOptionProviders { opt := provider() @@ -171,14 +165,14 @@ func testGetOptionsProvider(sameType Option) Option { return nil } -func testOptionsSerialize(opt Option) interface{} { +func testOptionsSerialize(t *testing.T, opt Option) interface{} { if opt == nil { return nil } var data interface{} buf := bytes.NewBuffer([]byte{}) - convey.So(json.NewEncoder(buf).Encode(opt), convey.ShouldBeNil) - convey.So(json.NewDecoder(buf).Decode(&data), convey.ShouldBeNil) + assert.Nil(t, json.NewEncoder(buf).Encode(opt), "no error should occur when serializing to JSON") + assert.Nil(t, json.NewDecoder(buf).Decode(&data), "no error should occur when deserializing from JSON") return data } diff --git a/packet.go b/packet.go index 5277f91..53b863d 100644 --- a/packet.go +++ b/packet.go @@ -1,10 +1,5 @@ package sentry -import ( - "bytes" - "encoding/json" -) - // A Packet is a JSON serializable object that will be sent to // the Sentry server to describe an event. It provides convinience // methods for setting options and handling the various types of @@ -64,7 +59,7 @@ func (p packet) setOption(option Option) { // If the option implements Finalize(), call it to give the // option the chance to prepare itself properly - if finalizable, ok := option.(FinalizableOption); ok { + if finalizable, ok := option.(FinalizeableOption); ok { finalizable.Finalize() } @@ -79,18 +74,4 @@ func (p packet) setOption(option Option) { } else { p[option.Class()] = option } -} - -func testSerializePacket(p Packet) (interface{}, error) { - buf := bytes.NewBuffer([]byte{}) - if err := json.NewEncoder(buf).Encode(p); err != nil { - return nil, err - } - - var data interface{} - if err := json.NewDecoder(buf).Decode(&data); err != nil { - return nil, err - } - - return data, nil -} +} \ No newline at end of file diff --git a/packet_test.go b/packet_test.go index d422fc5..2ac44b7 100644 --- a/packet_test.go +++ b/packet_test.go @@ -1,10 +1,11 @@ package sentry import ( + "bytes" "encoding/json" "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExamplePacket() { @@ -23,155 +24,112 @@ func ExamplePacket() { } func TestPacket(t *testing.T) { - Convey("Packet", t, func() { - Convey("NewPacket()", func() { - p := NewPacket() - So(p, ShouldNotBeNil) - So(p, ShouldImplement, (*Packet)(nil)) - }) + p := NewPacket() + assert.NotNil(t, p, "should return a non-nil packet") + assert.Implements(t, (*Packet)(nil), p, "it should implement the Packet interface") - Convey("Clone()", func() { - p := NewPacket() - p2 := p.Clone() + t.Run("SetOptions()", func(t *testing.T) { + assert.Equal(t, p, p.SetOptions(), "it should return the packet to support chaining") - So(p, ShouldNotEqual, p2) - So(p, ShouldResemble, p2) - }) + assert.Equal(t, p, p.SetOptions(nil), "it should ignore nil options") - Convey("SetOptions()", func() { - p := NewPacket() - So(p.SetOptions(), ShouldResemble, p) + opt := &testOption{} + assert.Equal(t, p.SetOptions(opt), p.Clone().SetOptions(nil, opt), "it should ignore nil options when other options are provided") - pp, ok := p.(*packet) - So(ok, ShouldBeTrue) - So(pp, ShouldNotBeNil) + pp, ok := p.(*packet) + assert.True(t, ok, "it should actually be a *packet") - pi := *pp + p.SetOptions(&testOption{}) + assert.Contains(t, *pp, "test", "it should contain the option field") + assert.Equal(t, &testOption{}, (*pp)["test"], "it should have the right value for the option field") - Convey("Should ignore nil options", func() { - Convey("When only nil options are provided", func() { - p2 := p.Clone() - So(p.SetOptions(nil), ShouldResemble, p2) - }) + t.Run("Option Replacement", func(t *testing.T) { + opt1 := &testOption{} + opt2 := &testOption{} - Convey("When both nil and non-nil options are provided", func() { - p2 := p.Clone() - opt := &testOption{} - So(p.SetOptions(nil, opt), ShouldResemble, p2.SetOptions(opt)) - }) - }) + p.SetOptions(opt1) + assert.Same(t, opt1, (*pp)["test"], "the first option should be set in the packet") - Convey("Should set normal option fields", func() { - opt := &testOption{} - p.SetOptions(opt) - So(pi, ShouldContainKey, "test") - So(pi["test"], ShouldEqual, opt) - }) + p.SetOptions(opt2) + assert.Same(t, opt2, (*pp)["test"], "the first option should be replaced by the second") + }) - Convey("Should obey the Omit() function", func() { - Convey("If it returns false", func() { - opt := &testOmitableOption{ - omit: false, - } - - p.SetOptions(opt) - So(pi, ShouldContainKey, "test") - So(pi["test"], ShouldEqual, opt) - }) - - Convey("If it returns true", func() { - opt := &testOmitableOption{ - omit: true, - } - - p.SetOptions(opt) - So(pi, ShouldNotContainKey, "test") - }) + t.Run("Omit()", func(t *testing.T) { + p.SetOptions(&testOmitableOption{ + omit: true, }) + assert.NotEqual(t, &testOmitableOption{omit: true}, (*pp)["test"], "it should omit changes if Omit() returns true") - Convey("Should use the Finalize() function", func() { - opt := &testFinalizableOption{} - So(opt.finalized, ShouldBeFalse) - - p.SetOptions(opt) - So(opt.finalized, ShouldBeTrue) + p.SetOptions(&testOmitableOption{ + omit: false, }) + assert.Equal(t, &testOmitableOption{omit: false}, (*pp)["test"], "it should not omit changes if Omit() returns false") + }) - Convey("Should handle existing keys", func() { - Convey("Should replace by default", func() { - opt1 := &testOption{} - opt2 := &testOption{} - - p.SetOptions(opt1) - So(pi, ShouldContainKey, "test") - So(pi["test"], ShouldEqual, opt1) - - p.SetOptions(opt2) - So(pi, ShouldContainKey, "test") - So(pi["test"], ShouldEqual, opt2) - }) - - Convey("Should merge when Merge() is present", func() { - opt1 := &testMergeableOption{data: 1} - opt2 := &testMergeableOption{data: 2} - - p.SetOptions(opt1) - So(pi, ShouldContainKey, "test") - So(pi["test"], ShouldEqual, opt1) - - p.SetOptions(opt2) - So(pi, ShouldContainKey, "test") - So(opt1.data, ShouldEqual, 1) - So(opt2.data, ShouldEqual, 2) - So(pi["test"], ShouldResemble, &testMergeableOption{data: 3}) - }) - }) + t.Run("Finalize()", func(t *testing.T) { + opt := &testFinalizeableOption{} + assert.False(t, opt.finalized, "the option should initially not be finalized") - Convey("Should use the Apply() function", func() { - opt := &testAdvancedOption{ - data: map[string]Option{ - "tested": Context("value", true), - }, - } + p.SetOptions(opt) + assert.True(t, opt.finalized, "the option should now be finalized") + assert.Equal(t, opt, (*pp)["test"], "the option should be stored in the packet") + }) - p.SetOptions(opt) - So(pi, ShouldContainKey, "tested") - So(pi["tested"], ShouldResemble, Context("value", true)) + t.Run("Merge()", func(t *testing.T) { + opt1 := &testMergeableOption{data: 1} + opt2 := &testMergeableOption{data: 2} - So(pi, ShouldNotContainKey, opt.Class()) - }) + p.SetOptions(opt1) + assert.Same(t, opt1, (*pp)["test"], "the packet should initially contain the first option") + + p.SetOptions(opt2) + assert.Equal(t, &testMergeableOption{data: 3}, (*pp)["test"], "the packet should then contain the merged option") + assert.Equal(t, 1, opt1.data, "the first option's data shouldn't be modified") + assert.Equal(t, 2, opt2.data, "the second option's data shouldn't be modified") }) - Convey("MarshalJSON", func() { - p := NewPacket() + t.Run("Apply()", func(t *testing.T) { + opt := &testAdvancedOption{ + data: map[string]Option{ + "tested": Context("value", true), + }, + } - Convey("With basic options", func() { - opt := &testOption{} - p.SetOptions(opt) + p.SetOptions(opt) + assert.Contains(t, (*pp), "tested", "it should have run the Apply() method") + assert.Equal(t, Context("value", true), (*pp)["tested"], "it should have stored the correct value") + }) + }) - b, err := json.Marshal(p) - So(err, ShouldBeNil) + t.Run("Clone()", func(t *testing.T) { + assert.False(t, p == p.Clone(), "it should clone to a new packet") + assert.Equal(t, p, p.Clone(), "it should clone to an equivalent packet") - var data map[string]interface{} - So(json.Unmarshal(b, &data), ShouldBeNil) + p := NewPacket().SetOptions(DSN(""), Message("Test")) + assert.Equal(t, p, p.Clone(), "the clone should copy any options across") + }) - So(data, ShouldContainKey, "test") - So(data["test"], ShouldResemble, map[string]interface{}{}) - }) + t.Run("MarshalJSON()", func(t *testing.T) { + p := NewPacket() + p.SetOptions(&testOption{}) + + assert.Equal(t, map[string]interface{}{ + "test": map[string]interface{}{}, + }, testSerializePacket(t, p)) - Convey("With custom MarshalJSON implementations", func() { - opt := &testSerializableOption{data: "testing"} - p.SetOptions(opt) + p.SetOptions(&testSerializableOption{data: "testing"}) + assert.Equal(t, map[string]interface{}{ + "test": "testing", + }, testSerializePacket(t, p)) + }) +} - b, err := json.Marshal(p) - So(err, ShouldBeNil) +func testSerializePacket(t *testing.T, p Packet) interface{} { + buf := bytes.NewBuffer([]byte{}) + assert.Nil(t, json.NewEncoder(buf).Encode(p), "it should not encounter any errors serializing the packet") - var data map[string]interface{} - So(json.Unmarshal(b, &data), ShouldBeNil) + var data interface{} + assert.Nil(t, json.NewDecoder(buf).Decode(&data), "it should not encounter any errors deserializing the packet") - So(data, ShouldContainKey, "test") - So(data["test"], ShouldResemble, "testing") - }) - }) - }) + return data } diff --git a/platform_test.go b/platform_test.go index 7863c24..e11a86b 100644 --- a/platform_test.go +++ b/platform_test.go @@ -1,10 +1,9 @@ package sentry import ( - "encoding/json" "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExamplePlatform() { @@ -20,26 +19,14 @@ func ExamplePlatform() { } func TestPlatform(t *testing.T) { - Convey("Platform", t, func() { - Convey("Should register itself with the default providers", func() { - opt := testGetOptionsProvider(Platform("go")) - So(opt, ShouldNotBeNil) - }) + assert.NotNil(t, testGetOptionsProvider(t, Platform("go")), "it should be registered as a default option") - Convey("Platform()", func() { - Convey("Should use the correct Class()", func() { - So(Platform("go").Class(), ShouldEqual, "platform") - }) + o := Platform("go") + assert.NotNil(t, o, "should not return a nil option") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "platform", o.Class(), "it should use the right option class") - Convey("MarshalJSON", func() { - Convey("Should marshal to a string", func() { - Convey("Should marshal to a string", func() { - b, err := json.Marshal(Platform("go")) - So(err, ShouldBeNil) - So(string(b), ShouldEqual, `"go"`) - }) - }) - }) - }) + t.Run("MarshalJSON()", func(t *testing.T) { + assert.Equal(t, "go", testOptionsSerialize(t, o), "it should serialize to a string") }) } diff --git a/queuedEvent_test.go b/queuedEvent_test.go index 0f30058..af476a9 100644 --- a/queuedEvent_test.go +++ b/queuedEvent_test.go @@ -5,7 +5,8 @@ import ( "testing" "time" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func ExampleQueuedEvent() { @@ -56,222 +57,259 @@ func ExampleQueuedEventInternal() { } func TestQueuedEvent(t *testing.T) { - Convey("QueuedEvent", t, func() { - id, err := NewEventID() - So(err, ShouldBeNil) + id, err := NewEventID() + require.Nil(t, err, "there should be no errors creating an event ID") - cl := NewClient( - DSN(""), - ) + cl := NewClient(DSN("")) + require.NotNil(t, cl, "the client should not be nil") - cfg, ok := cl.(Config) - So(ok, ShouldBeTrue) + cfg, ok := cl.(Config) + require.True(t, ok, "the client should implement the Config interface") - p := NewPacket().SetOptions(EventID(id)) + p := NewPacket().SetOptions(EventID(id)) + require.NotNil(t, p, "the packet should not be nil") - Convey("NewQueuedEvent()", func() { - e := NewQueuedEvent(cfg, p) - So(e, ShouldNotBeNil) + t.Run("NewQueuedEvent()", func(t *testing.T) { + e := NewQueuedEvent(cfg, p) + require.NotNil(t, e, "the event should not be nil") + assert.Implements(t, (*QueuedEvent)(nil), e, "it should implement the QueuedEvent interface") - ei, ok := e.(*queuedEvent) - So(ok, ShouldBeTrue) - So(ei.cfg, ShouldEqual, cfg) - So(ei.packet, ShouldEqual, p) - }) + ei, ok := e.(*queuedEvent) + require.True(t, ok, "it should actually be a *queuedEvent") + assert.Same(t, cfg, ei.cfg, "it should use the same config provider") + assert.Same(t, p, ei.packet, "it should use the same packet") - Convey("EventID()", func() { - Convey("With a valid packet", func() { - e := NewQueuedEvent(cfg, p) - So(e.EventID(), ShouldEqual, id) - }) + t.Run("EventID()", func(t *testing.T) { + assert.Equal(t, id, e.EventID(), "it should have the right event ID") - Convey("With an invalid packet", func() { - e := NewQueuedEvent(cfg, nil) - So(e.EventID(), ShouldEqual, "") - }) + assert.Empty(t, NewQueuedEvent(cfg, nil).EventID(), "it should have an empty EventID for an invalid packet") }) - Convey("Wait()", func() { - Convey("When it isn't yet complete", func(c C) { - e := NewQueuedEvent(cfg, p) - ch := make(chan struct{}) - defer close(ch) - - ei, ok := e.(QueuedEventInternal) - So(ok, ShouldBeTrue) - - go func() { - ch <- struct{}{} - e.Wait() - c.So(e.Error(), ShouldBeNil) - ch <- struct{}{} - }() - - // Wait for it to be waiting - <-ch - ei.Complete(nil) - - select { - case <-ch: - case <-time.After(100 * time.Millisecond): - So(fmt.Errorf("timeout"), ShouldBeNil) - } - }) - - Convey("When it is already complete", func(c C) { - e := NewQueuedEvent(cfg, p) - ch := make(chan struct{}) - defer close(ch) - - ei, ok := e.(QueuedEventInternal) - So(ok, ShouldBeTrue) - - ei.Complete(nil) - - go func() { - e.Wait() - c.So(e.Error(), ShouldBeNil) - ch <- struct{}{} - }() - - select { - case <-ch: - case <-time.After(100 * time.Millisecond): - So(fmt.Errorf("timeout"), ShouldBeNil) - } - }) + t.Run("Wait()", func(t *testing.T) { + cases := []struct { + Name string + Waiter func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) + PreWaiterStart func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) + PostWaiterStart func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) + }{ + { + Name: "SuccessSlow", + Waiter: func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) { + ch <- struct{}{} + ei.Wait() + assert.Nil(t, ei.Error(), "there should have been no error raised") + }, + PostWaiterStart: func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) { + <-ch + ei.Complete(nil) + }, + }, + { + Name: "SuccessFast", + Waiter: func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) { + ei.Wait() + assert.Nil(t, ei.Error(), "there should have been no error raised") + }, + PreWaiterStart: func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) { + ei.Complete(nil) + }, + }, + { + Name: "FailSlow", + Waiter: func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) { + ch <- struct{}{} + ei.Wait() + assert.EqualError(t, ei.Error(), "test error", "there should have been an error raised") + }, + PostWaiterStart: func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) { + <-ch + ei.Complete(fmt.Errorf("test error")) + }, + }, + { + Name: "FailFast", + Waiter: func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) { + ei.Wait() + assert.EqualError(t, ei.Error(), "test error", "there should have been an error raised") + }, + PreWaiterStart: func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) { + ei.Complete(fmt.Errorf("test error")) + }, + }, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + e := NewQueuedEvent(cfg, p) + require.NotNil(t, e, "the event should not be nil") + + require.Implements(t, (*QueuedEventInternal)(nil), e, "it should implement the QueuedEventInternal interface") + ei := e.(QueuedEventInternal) + + ch := make(chan struct{}) + defer close(ch) + + if tc.PreWaiterStart != nil { + tc.PreWaiterStart(t, ch, ei) + } + + go func() { + if tc.Waiter != nil { + tc.Waiter(t, ch, ei) + } + + ch <- struct{}{} + }() + + if tc.PostWaiterStart != nil { + tc.PostWaiterStart(t, ch, ei) + } + + select { + case <-ch: + case <-time.After(100 * time.Millisecond): + t.Error("timed out after 100ms with no response") + } + }) + } }) - Convey("WaitChannel()", func() { - Convey("When it isn't yet complete", func() { - e := NewQueuedEvent(cfg, p) - - ei, ok := e.(QueuedEventInternal) - So(ok, ShouldBeTrue) - - go func() { - ei.Complete(nil) - }() - - select { - case err := <-e.WaitChannel(): - So(err, ShouldBeNil) - case <-time.After(100 * time.Millisecond): - So(fmt.Errorf("timeout"), ShouldBeNil) - } - }) - - Convey("When it is already complete", func() { - e := NewQueuedEvent(cfg, p) - - ei, ok := e.(QueuedEventInternal) - So(ok, ShouldBeTrue) - - ei.Complete(nil) - - select { - case err := <-e.WaitChannel(): - So(err, ShouldBeNil) - case <-time.After(100 * time.Millisecond): - So(fmt.Errorf("timeout"), ShouldBeNil) - } - }) - - Convey("When it has an error", func() { - e := NewQueuedEvent(cfg, p) - - ei, ok := e.(QueuedEventInternal) - So(ok, ShouldBeTrue) - - err := fmt.Errorf("example error") - ei.Complete(err) - - select { - case err := <-e.WaitChannel(): - So(err, ShouldEqual, err) - case <-time.After(100 * time.Millisecond): - So(fmt.Errorf("timeout"), ShouldBeNil) - } - }) + t.Run("WaitChannel()", func(t *testing.T) { + cases := []struct { + Name string + Complete func(ei QueuedEventInternal) + Error error + }{ + {"SucceedFast", func(ei QueuedEventInternal) { ei.Complete(nil) }, nil}, + {"SucceedSlow", func(ei QueuedEventInternal) { go func() { ei.Complete(nil) }() }, nil}, + {"FailFast", func(ei QueuedEventInternal) { ei.Complete(fmt.Errorf("test error")) }, fmt.Errorf("test error")}, + {"FailSlow", func(ei QueuedEventInternal) { go func() { ei.Complete(fmt.Errorf("test error")) }() }, fmt.Errorf("test error")}, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + e := NewQueuedEvent(cfg, p) + require.NotNil(t, e, "the event should not be nil") + + require.Implements(t, (*QueuedEventInternal)(nil), e, "it should implement the QueuedEventInternal interface") + ei := e.(QueuedEventInternal) + + tc.Complete(ei) + + select { + case err := <-e.WaitChannel(): + if tc.Error != nil { + assert.EqualError(t, err, tc.Error.Error(), "the right error should have been raised") + } else { + assert.NoError(t, err, "no error should have been raised") + } + case <-time.After(100 * time.Millisecond): + t.Error("timeout after 100ms") + } + }) + } }) - Convey("Error()", func() { - Convey("When it isn't yet complete", func(c C) { - e := NewQueuedEvent(cfg, p) - ch := make(chan struct{}) - defer close(ch) - - ei, ok := e.(QueuedEventInternal) - So(ok, ShouldBeTrue) - - err := fmt.Errorf("example error") - - go func() { - ch <- struct{}{} - c.So(e.Error(), ShouldEqual, err) - ch <- struct{}{} - }() - - // Wait for it to be waiting - <-ch - ei.Complete(err) - - select { - case <-ch: - case <-time.After(100 * time.Millisecond): - So(fmt.Errorf("timeout"), ShouldBeNil) - } - }) - - Convey("When it is already complete", func(c C) { - e := NewQueuedEvent(cfg, p) - ch := make(chan struct{}) - defer close(ch) - - ei, ok := e.(QueuedEventInternal) - So(ok, ShouldBeTrue) - - err := fmt.Errorf("example error") - ei.Complete(err) - - go func() { - c.So(e.Error(), ShouldEqual, err) - ch <- struct{}{} - }() - - select { - case <-ch: - case <-time.After(100 * time.Millisecond): - So(fmt.Errorf("timeout"), ShouldBeNil) - } - }) + t.Run("Error()", func(t *testing.T) { + cases := []struct { + Name string + Waiter func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) + PreWaiterStart func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) + PostWaiterStart func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) + }{ + { + Name: "SuccessSlow", + Waiter: func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) { + ch <- struct{}{} + assert.Nil(t, ei.Error(), "there should have been no error raised") + }, + PostWaiterStart: func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) { + <-ch + ei.Complete(nil) + }, + }, + { + Name: "SuccessFast", + Waiter: func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) { + assert.Nil(t, ei.Error(), "there should have been no error raised") + }, + PreWaiterStart: func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) { + ei.Complete(nil) + }, + }, + { + Name: "FailSlow", + Waiter: func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) { + ch <- struct{}{} + assert.EqualError(t, ei.Error(), "test error", "there should have been an error raised") + }, + PostWaiterStart: func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) { + <-ch + ei.Complete(fmt.Errorf("test error")) + }, + }, + { + Name: "FailFast", + Waiter: func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) { + assert.EqualError(t, ei.Error(), "test error", "there should have been an error raised") + }, + PreWaiterStart: func(t *testing.T, ch chan struct{}, ei QueuedEventInternal) { + ei.Complete(fmt.Errorf("test error")) + }, + }, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + e := NewQueuedEvent(cfg, p) + require.NotNil(t, e, "the event should not be nil") + + require.Implements(t, (*QueuedEventInternal)(nil), e, "it should implement the QueuedEventInternal interface") + ei := e.(QueuedEventInternal) + + ch := make(chan struct{}) + defer close(ch) + + if tc.PreWaiterStart != nil { + tc.PreWaiterStart(t, ch, ei) + } + + go func() { + if tc.Waiter != nil { + tc.Waiter(t, ch, ei) + } + + ch <- struct{}{} + }() + + if tc.PostWaiterStart != nil { + tc.PostWaiterStart(t, ch, ei) + } + + select { + case <-ch: + case <-time.After(100 * time.Millisecond): + t.Error("timed out after 100ms with no response") + } + }) + } }) + }) - Convey("QueuedEventInternal", func() { - e := NewQueuedEvent(cfg, p) - So(e, ShouldNotBeNil) - - ei, ok := e.(QueuedEventInternal) - So(ok, ShouldBeTrue) + t.Run("Complete()", func(t *testing.T) { + e := NewQueuedEvent(cfg, p) + require.NotNil(t, e, "the event should not be nil") - Convey("Complete()", func() { - Convey("Should set the error", func() { - err := fmt.Errorf("example error") - ei.Complete(err) - So(e.Error(), ShouldEqual, err) - }) + require.Implements(t, (*QueuedEventInternal)(nil), e, "it should implement the QueuedEventInternal interface") + ei := e.(QueuedEventInternal) - Convey("When called multiple times", func() { - ei.Complete(nil) - ei.Complete(nil) - So(e.Error(), ShouldBeNil) + ei.Complete(fmt.Errorf("test error")) + assert.EqualError(t, e.Error(), "test error", "it should set the error correctly") - Convey("Should not change the event's details", func() { - ei.Complete(fmt.Errorf("example error")) - So(e.Error(), ShouldBeNil) - }) - }) - }) - }) + ei.Complete(nil) + assert.NotNil(t, e.Error(), "it shouldn't modify the status of the event after it has been set") }) } diff --git a/release_test.go b/release_test.go index ca8ae5c..d355369 100644 --- a/release_test.go +++ b/release_test.go @@ -1,10 +1,9 @@ package sentry import ( - "encoding/json" "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleRelease() { @@ -20,21 +19,12 @@ func ExampleRelease() { } func TestRelease(t *testing.T) { - Convey("Release", t, func() { - Convey("Release()", func() { - Convey("Should use the correct Class()", func() { - So(Release("test").Class(), ShouldEqual, "release") - }) + o := Release("test") + assert.NotNil(t, o, "should not return a nil option") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "release", o.Class(), "it should use the right option class") - Convey("MarshalJSON", func() { - Convey("Should marshal to a string", func() { - Convey("Should marshal to a string", func() { - b, err := json.Marshal(Release("test")) - So(err, ShouldBeNil) - So(string(b), ShouldEqual, `"test"`) - }) - }) - }) - }) + t.Run("MarshalJSON()", func(t *testing.T) { + assert.Equal(t, "test", testOptionsSerialize(t, o), "it should serialize to a string") }) } diff --git a/sdk_test.go b/sdk_test.go index af690b2..01fad00 100644 --- a/sdk_test.go +++ b/sdk_test.go @@ -3,30 +3,20 @@ package sentry import ( "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func TestSDKOption(t *testing.T) { - Convey("SDK Option", t, func() { - Convey("Should register itself with the default providers", func() { - opt := testGetOptionsProvider(&sdkOption{}) - So(opt, ShouldNotBeNil) - }) + o := testGetOptionsProvider(t, &sdkOption{}) + assert.NotNil(t, o, "it should be registered as a default option") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "sdk", o.Class(), "it should use the right option class") - Convey("Should register with the correct name", func() { - opt := testGetOptionsProvider(&sdkOption{}) - So(opt, ShouldNotBeNil) - - oo := opt.(*sdkOption) - So(oo.Name, ShouldEqual, "SierraSoftworks/sentry-go") - }) - - Convey("Should register with the correct version", func() { - opt := testGetOptionsProvider(&sdkOption{}) - So(opt, ShouldNotBeNil) - - oo := opt.(*sdkOption) - So(oo.Version, ShouldEqual, version) - }) + t.Run("MarshalJSON()", func(t *testing.T) { + assert.Equal(t, map[string]interface{}{ + "integrations": []interface{}{}, + "name": "SierraSoftworks/sentry-go", + "version": version, + }, testOptionsSerialize(t, o), "it should serialize to a string") }) } diff --git a/sendQueue_test.go b/sendQueue_test.go index 45a81a0..158f63d 100644 --- a/sendQueue_test.go +++ b/sendQueue_test.go @@ -3,7 +3,7 @@ package sentry import ( "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleUseSendQueue() { @@ -20,28 +20,16 @@ func ExampleUseSendQueue() { } func TestSendQueue(t *testing.T) { - Convey("SendQueue", t, func() { - Convey("UseSendQueue()", func() { - Convey("Should return an Option", func() { - q := NewSequentialSendQueue(0) - So(UseSendQueue(q), ShouldImplement, (*Option)(nil)) - }) - - Convey("Should return nil if no queue is provided", func() { - So(UseSendQueue(nil), ShouldEqual, nil) - }) - - Convey("Should use the correct Class()", func() { - q := NewSequentialSendQueue(0) - So(UseSendQueue(q).Class(), ShouldEqual, "sentry-go.sendqueue") - }) - - Convey("Should implement Omit() and always return true", func() { - q := NewSequentialSendQueue(0) - o := UseSendQueue(q) - So(o, ShouldImplement, (*OmitableOption)(nil)) - So(o.(OmitableOption).Omit(), ShouldBeTrue) - }) - }) - }) + assert.Nil(t, UseSendQueue(nil), "it should return nil if no transport is provided") + + q := NewSequentialSendQueue(0) + o := UseSendQueue(q) + assert.NotNil(t, o, "should not return a nil option") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "sentry-go.sendqueue", o.Class(), "it should use the right option class") + + if assert.Implements(t, (*Option)(nil), o, "it should implement the OmitableOption interface") { + oo := o.(OmitableOption) + assert.True(t, oo.Omit(), "it should always return true for calls to Omit()") + } } diff --git a/sequentialSendQueue_test.go b/sequentialSendQueue_test.go index ab5ef1e..3fb8655 100644 --- a/sequentialSendQueue_test.go +++ b/sequentialSendQueue_test.go @@ -1,131 +1,122 @@ package sentry import ( - "fmt" "testing" "time" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestSequentialSendQueue(t *testing.T) { - Convey("SequentialSendQueue", t, func() { - Convey("NewSequentialSendQueue()", func() { + q := NewSequentialSendQueue(10) + require.NotNil(t, q, "the queue should not be nil") + assert.Implements(t, (*SendQueue)(nil), q, "it should implement the SendQueue interface") + defer q.Shutdown(true) + + require.IsType(t, &sequentialSendQueue{}, q, "it should actually be a *sequentialSendQueue") + + t.Run("Send()", func(t *testing.T) { + dsn := "http://user:pass@example.com/sentry/1" + transport := testNewTestTransport() + require.NotNil(t, transport, "the transport should not be nil") + + cl := NewClient(DSN(dsn), UseTransport(transport)) + require.NotNil(t, cl, "the client should not be nil") + + cfg, ok := cl.(Config) + require.True(t, ok, "the client should implement the Config interface") + + p := NewPacket() + require.NotNil(t, p, "the packet should not be nil") + + t.Run("Normal", func(t *testing.T) { q := NewSequentialSendQueue(10) - So(q, ShouldNotBeNil) - So(q, ShouldImplement, (*SendQueue)(nil)) + require.NotNil(t, q, "the queue should not be nil") defer q.Shutdown(true) - So(q, ShouldHaveSameTypeAs, &sequentialSendQueue{}) + e := q.Enqueue(cfg, p) + require.NotNil(t, e, "the event should not be nil") + + select { + case pp := <-transport.ch: + assert.Equal(t, p, pp, "the packet which was sent should match the packet which was enqueued") + case <-time.After(100 * time.Millisecond): + t.Fatal("timed out waiting for send") + } + + select { + case err, ok := <-e.WaitChannel(): + assert.False(t, ok, "the channel should have been closed") + assert.NoError(t, err, "there should have been no error sending the event") + case <-time.After(100 * time.Millisecond): + t.Fatal("timed out waiting for event completion") + } }) - Convey("Send()", func() { - dsn := "http://user:pass@example.com/sentry/1" - transport := testNewTestTransport() - So(transport, ShouldNotBeNil) - - cl := NewClient( - DSN(dsn), - UseTransport(transport), - ) - - cfg, ok := cl.(Config) - So(ok, ShouldBeTrue) - - Convey("Normal Operation", func() { - q := NewSequentialSendQueue(10) - So(q, ShouldNotBeNil) - defer q.Shutdown(true) - - p := NewPacket() - So(p, ShouldNotBeNil) - - e := q.Enqueue(cfg, p) - So(e, ShouldNotBeNil) - - select { - case p2 := <-transport.ch: - So(p2, ShouldEqual, p) - case <-time.After(100 * time.Millisecond): - So(fmt.Errorf("timeout"), ShouldBeNil) - } - - select { - case err, ok := <-e.WaitChannel(): - So(err, ShouldBeNil) - So(ok, ShouldBeFalse) - case <-time.After(100 * time.Millisecond): - So(fmt.Errorf("timeout"), ShouldBeNil) - } - }) - - Convey("Buffer Overflow", func() { - q := NewSequentialSendQueue(0) - So(q, ShouldNotBeNil) - defer q.Shutdown(true) - - p := NewPacket() - So(p, ShouldNotBeNil) - - time.Sleep(1 * time.Millisecond) - - // First entry will be processed - e1 := q.Enqueue(cfg, p) - So(e1, ShouldNotBeNil) - - // Second will be failed - e2 := q.Enqueue(cfg, p) - So(e2, ShouldNotBeNil) - - select { - case <-transport.ch: - So(e1.Error(), ShouldBeNil) - case err := <-e1.WaitChannel(): - So(err, ShouldBeNil) - case <-time.After(100 * time.Millisecond): - So(fmt.Errorf("timeout"), ShouldBeNil) - } - - select { - case <-transport.ch: - So(fmt.Errorf("shouldn't send"), ShouldBeNil) - case err := <-e2.WaitChannel(): - So(err, ShouldNotBeNil) - So(err.Error(), ShouldContainSubstring, ErrSendQueueFull.Error()) - case <-time.After(100 * time.Millisecond): - So(fmt.Errorf("timeout"), ShouldBeNil) - } - }) - - Convey("When Shutdown", func() { - q := NewSequentialSendQueue(10) - So(q, ShouldNotBeNil) - q.Shutdown(true) - - p := NewPacket() - So(p, ShouldNotBeNil) - - e := q.Enqueue(cfg, p) - So(e, ShouldNotBeNil) - - select { - case <-transport.ch: - So(fmt.Errorf("shouldn't send"), ShouldBeNil) - case err := <-e.WaitChannel(): - So(err, ShouldNotBeNil) - So(err.Error(), ShouldContainSubstring, ErrSendQueueShutdown.Error()) - case <-time.After(100 * time.Millisecond): - So(fmt.Errorf("timeout"), ShouldBeNil) - } - }) + t.Run("QueueFull", func(t *testing.T) { + q := NewSequentialSendQueue(0) + require.NotNil(t, q, "the queue should not be nil") + defer q.Shutdown(true) + + // Give the queue time to start + time.Sleep(1 * time.Millisecond) + + e1 := q.Enqueue(cfg, p) + require.NotNil(t, e1, "the event should not be nil") + + e2 := q.Enqueue(cfg, p) + require.NotNil(t, e2, "the event should not be nil") + + select { + case pp := <-transport.ch: + assert.Equal(t, p, pp, "the packet which was sent should match the packet which was enqueued") + assert.Nil(t, e1.Error(), "") + case err, ok := <-e1.WaitChannel(): + assert.False(t, ok, "the channel should have been closed") + assert.NoError(t, err, "there should have been no error sending the event") + case <-time.After(100 * time.Millisecond): + t.Fatal("timed out waiting for send") + } + + select { + case <-transport.ch: + t.Error("the transport should never have received the event for sending") + case err, ok := <-e2.WaitChannel(): + assert.True(t, ok, "the channel should not have been closed prematurely") + assert.True(t, ErrSendQueueFull.IsInstance(err), "the error should be of type ErrSendQueueFull") + case <-time.After(100 * time.Millisecond): + t.Fatal("timed out waiting for event completion") + } }) - Convey("Shutdown()", func() { - Convey("Should be safe to call repeatedly", func() { - q := NewSequentialSendQueue(0) - q.Shutdown(true) - q.Shutdown(true) - }) + t.Run("Shutdown", func(t *testing.T) { + q := NewSequentialSendQueue(10) + require.NotNil(t, q, "the queue should not be nil") + + // Shutdown the queue + q.Shutdown(true) + + e := q.Enqueue(cfg, p) + require.NotNil(t, e, "the event should not be nil") + + select { + case <-transport.ch: + t.Error("the transport should never have received the event for sending") + case err, ok := <-e.WaitChannel(): + assert.True(t, ok, "the channel should not have been closed prematurely") + assert.True(t, ErrSendQueueShutdown.IsInstance(err), "the error should be of type ErrSendQueueShutdown") + case <-time.After(100 * time.Millisecond): + t.Fatal("timed out waiting for event completion") + } }) }) + + t.Run("Shutdown()", func(t *testing.T) { + q := NewSequentialSendQueue(10) + + // It should be safe to call this repeatedly + q.Shutdown(true) + q.Shutdown(true) + }) } diff --git a/servername_test.go b/servername_test.go index 7928f83..88cb959 100644 --- a/servername_test.go +++ b/servername_test.go @@ -1,10 +1,9 @@ package sentry import ( - "encoding/json" "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleServerName() { @@ -20,26 +19,14 @@ func ExampleServerName() { } func TestServerName(t *testing.T) { - Convey("ServerName", t, func() { - Convey("Should register itself with the default providers", func() { - opt := testGetOptionsProvider(ServerName("")) - So(opt, ShouldNotBeNil) - }) + assert.NotNil(t, testGetOptionsProvider(t, ServerName("")), "it should be registered as a default option") - Convey("ServerName()", func() { - Convey("Should use the correct Class()", func() { - So(ServerName("test").Class(), ShouldEqual, "server_name") - }) + o := ServerName("test") + assert.NotNil(t, o, "should not return a nil option") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "server_name", o.Class(), "it should use the right option class") - Convey("MarshalJSON", func() { - Convey("Should marshal to a string", func() { - Convey("Should marshal to a string", func() { - b, err := json.Marshal(ServerName("test")) - So(err, ShouldBeNil) - So(string(b), ShouldEqual, `"test"`) - }) - }) - }) - }) + t.Run("MarshalJSON()", func(t *testing.T) { + assert.Equal(t, "test", testOptionsSerialize(t, o), "it should serialize to a string") }) } diff --git a/severity_test.go b/severity_test.go index b38cd16..ca6e1b8 100644 --- a/severity_test.go +++ b/severity_test.go @@ -1,10 +1,9 @@ package sentry import ( - "encoding/json" "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleLevel() { @@ -20,39 +19,20 @@ func ExampleLevel() { } func TestSeverity(t *testing.T) { - Convey("Severity", t, func() { - Convey("Level()", func() { - Convey("Should use the correct Class()", func() { - So(Level(Error).Class(), ShouldEqual, "level") - }) - - Convey("MarshalJSON", func() { - Convey("Should marshal to a string", func() { - b, err := json.Marshal(Level(Error)) - So(err, ShouldBeNil) - So(string(b), ShouldEqual, `"error"`) - }) - }) - }) - - Convey("Fatal should use the correct name", func() { - So(string(Fatal), ShouldEqual, "fatal") - }) - - Convey("Error should use the correct name", func() { - So(string(Error), ShouldEqual, "error") - }) - - Convey("Warning should use the correct name", func() { - So(string(Warning), ShouldEqual, "warning") - }) - - Convey("Info should use the correct name", func() { - So(string(Info), ShouldEqual, "info") - }) - - Convey("Debug should use the correct name", func() { - So(string(Debug), ShouldEqual, "debug") - }) + assert.NotNil(t, testGetOptionsProvider(t, Level(Info)), "it should be registered as a default option") + + o := Level(Error) + assert.NotNil(t, o, "should not return a nil option") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "level", o.Class(), "it should use the right option class") + + t.Run("MarshalJSON()", func(t *testing.T) { + assert.Equal(t, "error", testOptionsSerialize(t, o), "it should serialize to a string") }) + + assert.EqualValues(t, Fatal, "fatal", "fatal should use the correct name") + assert.EqualValues(t, Error, "error", "fatal should use the correct name") + assert.EqualValues(t, Warning, "warning", "fatal should use the correct name") + assert.EqualValues(t, Info, "info", "fatal should use the correct name") + assert.EqualValues(t, Debug, "debug", "fatal should use the correct name") } diff --git a/stacktraceGen_test.go b/stacktraceGen_test.go index a8d0c1c..46ed223 100644 --- a/stacktraceGen_test.go +++ b/stacktraceGen_test.go @@ -6,102 +6,105 @@ import ( "testing" "github.com/pkg/errors" - - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestStackTraceGenerator(t *testing.T) { - Convey("StackTrace Generator", t, func() { - Convey("ForError", func() { - Convey("With .StackTrace()", func() { - err := errors.New("test error") - frames := getStacktraceFramesForError(err) - So(frames.Len(), ShouldBeGreaterThan, 0) - So(frames[frames.Len()-1].Function, ShouldEqual, "TestStackTraceGenerator.func1.1.1") - }) + t.Run("getStacktraceFramesForError()", func(t *testing.T) { + t.Run("StackTraceableError", func(t *testing.T) { + err := errors.New("test error") + frames := getStacktraceFramesForError(err) + if assert.NotEmpty(t, frames, "there should be frames from the error") { + assert.Equal(t, "TestStackTraceGenerator.func1.1", frames[frames.Len()-1].Function, "it should have the right function name as the top-most frame") + } + }) - Convey("Without .StackTrace()", func() { - err := fmt.Errorf("test error") - frames := getStacktraceFramesForError(err) - So(frames.Len(), ShouldEqual, 0) - }) + t.Run("error", func(t *testing.T) { + err := fmt.Errorf("test error") + frames := getStacktraceFramesForError(err) + assert.Empty(t, frames, "there should be no frames from a normal error") }) + }) - Convey("ForCurrentContext", func() { - Convey("For the current function", func() { - frames := getStacktraceFrames(0) - So(frames.Len(), ShouldBeGreaterThan, 3) - So(frames[frames.Len()-1].Function, ShouldEqual, "TestStackTraceGenerator.func1.2.1") - }) + t.Run("getStacktraceFrames()", func(t *testing.T) { + t.Run("Skip", func(t *testing.T) { + frames := getStacktraceFrames(999999999) + assert.Empty(t, frames, "with an extreme skip, there should be no frames") + }) - Convey("With an extreme skip", func() { - frames := getStacktraceFrames(999999999) - So(frames.Len(), ShouldEqual, 0) - }) + t.Run("Current Function", func(t *testing.T) { + frames := getStacktraceFrames(0) + if assert.NotEmpty(t, frames, "there should be frames from the current function") { + assert.Equal(t, "TestStackTraceGenerator.func2.2", frames[frames.Len()-1].Function, "it should have the right function name as the top-most frame") + } }) + }) - Convey("getStacktraceFrame()", func() { - pc, file, line, ok := runtime.Caller(0) - So(ok, ShouldBeTrue) + t.Run("getStackTraceFrame()", func(t *testing.T) { + pc, file, line, ok := runtime.Caller(0) + require.True(t, ok, "we should be able to get the current caller") - frame := getStacktraceFrame(pc) - So(frame, ShouldNotBeNil) - So(frame.AbsoluteFilename, ShouldEqual, file) - So(frame.Line, ShouldEqual, line) + frame := getStacktraceFrame(pc) + require.NotNil(t, frame, "the frame should not be nil") - So(frame.Filename, ShouldEqual, "github.com/SierraSoftworks/sentry-go/stacktraceGen_test.go") - So(frame.Function, ShouldStartWith, "TestStackTraceGenerator.func1.3") - So(frame.Module, ShouldEqual, "sentry-go") - So(frame.Package, ShouldEqual, "github.com/SierraSoftworks/sentry-go") - }) + assert.Equal(t, file, frame.AbsoluteFilename, "the filename for the frame should match the caller") + assert.Equal(t, line, frame.Line, "the line from the frame should match the caller") - Convey("stacktraceFrame", func() { - Convey("ClassifyInternal", func() { - frames := getStacktraceFrames(0) - So(frames.Len(), ShouldBeGreaterThan, 3) + assert.Equal(t, "github.com/SierraSoftworks/sentry-go/stacktraceGen_test.go", frame.Filename, "it should have the correct filename") + assert.Equal(t, "TestStackTraceGenerator.func3", frame.Function, "it should have the correct function name") + assert.Equal(t, "sentry-go", frame.Module, "it should have the correct module name") + assert.Equal(t, "github.com/SierraSoftworks/sentry-go", frame.Package, "it should have the correct package name") + }) - for _, frame := range frames { - frame.ClassifyInternal([]string{"github.com/SierraSoftworks/sentry-go"}) - } + t.Run("stackTraceFrame.ClassifyInternal()", func(t *testing.T) { + frames := getStacktraceFrames(0) + require.Greater(t, frames.Len(), 3, "the number of frames should be more than 3") - So(frames[frames.Len()-1].InApp, ShouldBeTrue) - So(frames[0].InApp, ShouldBeFalse) - }) - }) + for i, frame := range frames { + assert.False(t, frame.InApp, "all frames should initially be marked as external (frame index = %d)", i) + frame.ClassifyInternal([]string{"github.com/SierraSoftworks/sentry-go"}) + } - Convey("formatFuncName()", func() { - Convey("With a full package name", func() { - pack, module, name := formatFuncName("github.com/SierraSoftworks/sentry-go.Context") - So(pack, ShouldEqual, "github.com/SierraSoftworks/sentry-go") - So(module, ShouldEqual, "sentry-go") - So(name, ShouldEqual, "Context") - }) + assert.True(t, frames[frames.Len()-1].InApp, "the top-most frame should be marked as internal (this function)") + assert.False(t, frames[0].InApp, "the bottom-most frame should be marked as external (the test harness main method)") + }) - Convey("With no package", func() { - pack, module, name := formatFuncName("sentry-go.Context") - So(pack, ShouldEqual, "sentry-go") - So(module, ShouldEqual, "sentry-go") - So(name, ShouldEqual, "Context") + t.Run("formatFuncName()", func(t *testing.T) { + cases := []struct { + Name string + + FullName string + Package string + Module string + FunctionName string + }{ + {"Full Name", "github.com/SierraSoftworks/sentry-go.Context", "github.com/SierraSoftworks/sentry-go", "sentry-go", "Context"}, + {"Struct Function Name", "github.com/SierraSoftworks/sentry-go.packet.Clone", "github.com/SierraSoftworks/sentry-go", "sentry-go", "packet.Clone"}, + {"No Package", "sentry-go.Context", "sentry-go", "sentry-go", "Context"}, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + pack, module, name := formatFuncName(tc.FullName) + assert.Equal(t, tc.Package, pack, "the package name should be correct") + assert.Equal(t, tc.Module, module, "the module name should be correct") + assert.Equal(t, tc.FunctionName, name, "the function name should be correct") }) - }) + } + }) - Convey("shortFilename()", func() { + t.Run("shortFilename()", func(t *testing.T) { + t.Run("GOPATH", func(t *testing.T) { GOPATH := "/go/src" pkg := "github.com/SierraSoftworks/sentry-go" file := "stacktraceGen_test.go" filename := fmt.Sprintf("%s/%s/%s", GOPATH, pkg, file) - Convey("With no package", func() { - So(shortFilename(filename, ""), ShouldEqual, filename) - }) - - Convey("With a valid package path", func() { - So(shortFilename(filename, pkg), ShouldEqual, fmt.Sprintf("%s/%s", pkg, file)) - }) - - Convey("With an invalid package path", func() { - So(shortFilename(filename, "cithub.com/SierraSoftworks/bender"), ShouldEqual, filename) - }) + assert.Equal(t, filename, shortFilename(filename, ""), "should use the original filename if no package is provided") + assert.Equal(t, filename, shortFilename(filename, "bitblob.com/bender"), "should use the original filename if the package name doesn't match the path") + assert.Equal(t, fmt.Sprintf("%s/%s", pkg, file), shortFilename(filename, pkg), "should use the $pkg/$file if the package is provided") }) }) } diff --git a/stacktrace_test.go b/stacktrace_test.go index c4f6cae..773e435 100644 --- a/stacktrace_test.go +++ b/stacktrace_test.go @@ -4,7 +4,8 @@ import ( "testing" "github.com/pkg/errors" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func ExampleAddInternalPrefixes() { @@ -31,60 +32,43 @@ func ExampleStackTrace() { ) } -func TestStackTrace(t *testing.T) { - Convey("StackTrace", t, func() { - Convey("AddInternalPrefixes()", func() { - AddInternalPrefixes("github.com/SierraSoftworks/sentry-go") - So(defaultInternalPrefixes, ShouldResemble, []string{"main", "github.com/SierraSoftworks/sentry-go"}) - }) - - Convey("StackTrace()", func() { - Convey("Should return an Option", func() { - So(StackTrace(), ShouldImplement, (*Option)(nil)) - }) - - Convey("Should let you collect stacktrace frames from an error", func() { - st := StackTrace() - So(st, ShouldNotBeNil) - - err := errors.New("example error") - So(st.ForError(err), ShouldEqual, st) - }) +func TestAddInternalPrefixes(t *testing.T) { + assert.Contains(t, defaultInternalPrefixes, "main") + AddInternalPrefixes("github.com/SierraSoftworks/sentry-go") + assert.Contains(t, defaultInternalPrefixes, "github.com/SierraSoftworks/sentry-go") +} - Convey("Should allow you to set internal package prefixes", func() { - st := StackTrace() - So(st, ShouldNotBeNil) +func TestStackTrace(t *testing.T) { + o := StackTrace() + require.NotNil(t, o, "it should return a non-nil option") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "stacktrace", o.Class(), "it should use the right option class") - sti, ok := st.(*stackTraceOption) - So(ok, ShouldBeTrue) - So(sti.internalPrefixes, ShouldResemble, defaultInternalPrefixes) + sti, ok := o.(*stackTraceOption) + require.True(t, ok, "it should actually be a *stackTraceOption") - st.WithInternalPrefixes("github.com/SierraSoftworks/sentry-go") - st.WithInternalPrefixes("github.com/SierraSoftworks") - So(sti.internalPrefixes, ShouldContain, "github.com/SierraSoftworks") - So(sti.internalPrefixes, ShouldContain, "github.com/SierraSoftworks/sentry-go") - }) - }) + assert.NotEmpty(t, sti.Frames, "it should start off with your current stack frames") + originalFrames := sti.Frames - Convey("Should implement Finalize()", func() { - So(StackTrace(), ShouldImplement, (*FinalizableOption)(nil)) - }) + err := errors.New("example error") + assert.Same(t, o, o.ForError(err), "it should reuse the same instance when adding error information") + assert.NotEmpty(t, sti.Frames, "it should have loaded frame information from the error") + assert.NotEqual(t, originalFrames, sti.Frames, "the frames should not be the original ones it started with") - Convey("Finalize()", func() { - st := StackTrace().WithInternalPrefixes("github.com/SierraSoftworks/sentry-go") - So(st, ShouldNotBeNil) + assert.Equal(t, defaultInternalPrefixes, sti.internalPrefixes, "it should start out with the default internal prefixes") - sti, ok := st.(*stackTraceOption) - So(ok, ShouldBeTrue) + o.WithInternalPrefixes("github.com/SierraSoftworks") + assert.Contains(t, sti.internalPrefixes, "github.com/SierraSoftworks", "it should allow you to add new internal prefixes") - sti.Finalize() + if assert.Implements(t, (*FinalizeableOption)(nil), o, "it should implement the FinalizeableOption interface") { + for i, frame := range sti.Frames { + assert.False(t, frame.InApp, "all frames should initially be marked as external (frame index=%d)", i) + } - So(len(sti.Frames), ShouldBeGreaterThan, 0) - So(sti.Frames[len(sti.Frames)-1].InApp, ShouldBeTrue) - }) + sti.Finalize() - Convey("Should use the correct Class()", func() { - So(StackTrace().Class(), ShouldEqual, "stacktrace") - }) - }) + if assert.NotEmpty(t, sti.Frames, "the frames list should not be empty") { + assert.True(t, sti.Frames[len(sti.Frames)-1].InApp, "the final frame should be marked as internal") + } + } } diff --git a/tags_test.go b/tags_test.go index 61ae658..ea28e50 100644 --- a/tags_test.go +++ b/tags_test.go @@ -3,7 +3,8 @@ package sentry import ( "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func ExampleTags() { @@ -25,96 +26,68 @@ func ExampleTags() { } func TestTags(t *testing.T) { - Convey("Tags", t, func() { - Convey("Tags()", func() { - data := map[string]string{ - "redis": "1.0.0", - } - - Convey("Should return nil if the data is nil", func() { - So(Tags(nil), ShouldBeNil) - }) - - Convey("Should return an Option", func() { - So(Tags(data), ShouldImplement, (*Option)(nil)) - }) - - Convey("Should use the correct Class()", func() { - So(Tags(data).Class(), ShouldEqual, "tags") - }) + assert.Nil(t, Tags(nil), "it should return nil if the data provided is nil") - Convey("Should implement Merge()", func() { - So(Tags(data), ShouldImplement, (*MergeableOption)(nil)) - }) - }) + data := map[string]string{ + "redis": "1.0.0", + } - Convey("Merge()", func() { - data1 := map[string]string{ - "redis": "1.0.0", - } - e1 := Tags(data1) - So(e1, ShouldNotBeNil) + o := Tags(data) + require.NotNil(t, o, "it should not return nil if the data is non-nil") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "tags", o.Class(), "it should use the right option class") - e1m, ok := e1.(MergeableOption) - So(ok, ShouldBeTrue) + if assert.Implements(t, (*MergeableOption)(nil), o, "it should implement the MergeableOption interface") { + t.Run("Merge()", func(t *testing.T) { + om := o.(MergeableOption) - Convey("Should overwrite if it doesn't recognize the old option", func() { - So(e1m.Merge(&testOption{}), ShouldEqual, e1) - }) + assert.Equal(t, o, om.Merge(&testOption{}), "it should replace the old option if it is not recognized") - Convey("Should merge multiple modules entries", func() { + t.Run("different entries", func(t *testing.T) { data2 := map[string]string{ "pgsql": "5.4.0", } + o2 := Tags(data2) + require.NotNil(t, o2, "the second module option should not be nil") - e2 := Tags(data2) - So(e2, ShouldNotBeNil) + oo := om.Merge(o2) + require.NotNil(t, oo, "it should not return nil when it merges") - em := e1m.Merge(e2) - So(em, ShouldNotBeNil) - So(em, ShouldNotEqual, e1) - So(em, ShouldNotEqual, e2) + ooi, ok := oo.(*tagsOption) + require.True(t, ok, "it should actually be a *tagsOption") - emm, ok := em.(*tagsOption) - So(ok, ShouldBeTrue) - So(emm.tags, ShouldContainKey, "redis") - So(emm.tags, ShouldContainKey, "pgsql") + if assert.Contains(t, ooi.tags, "redis", "it should contain the first key") { + assert.Equal(t, data["redis"], ooi.tags["redis"], "it should have the right value for the first key") + } + + if assert.Contains(t, ooi.tags, "pgsql", "it should contain the second key") { + assert.Equal(t, data2["pgsql"], ooi.tags["pgsql"], "it should have the right value for the second key") + } }) - Convey("Should overwrite old entries with new ones", func() { + t.Run("existing entries", func(t *testing.T) { data2 := map[string]string{ "redis": "0.8.0", } - e2 := Tags(data2) - So(e2, ShouldNotBeNil) - - em := e1m.Merge(e2) - So(em, ShouldNotBeNil) - So(em, ShouldNotEqual, e1) - So(em, ShouldNotEqual, e2) - - emm, ok := em.(*tagsOption) - So(ok, ShouldBeTrue) - So(emm.tags, ShouldContainKey, "redis") - So(emm.tags["redis"], ShouldEqual, "1.0.0") - }) - }) + o2 := Tags(data2) + require.NotNil(t, o2, "the second module option should not be nil") - Convey("MarshalJSON", func() { - Convey("Should marshal the fields correctly", func() { - data := map[string]string{ - "redis": "1.0.0", - } + oo := om.Merge(o2) + require.NotNil(t, oo, "it should not return nil when it merges") - serialized := testOptionsSerialize(Tags(data)) - So(serialized, ShouldNotBeNil) + ooi, ok := oo.(*tagsOption) + require.True(t, ok, "it should actually be a *modulesOption") - expected := map[string]interface{}{ - "redis": "1.0.0", + if assert.Contains(t, ooi.tags, "redis", "it should contain the first key") { + assert.Equal(t, data["redis"], ooi.tags["redis"], "it should have the right value for the first key") } - So(serialized, ShouldHaveSameTypeAs, expected) - So(serialized, ShouldResemble, expected) }) }) + } + + t.Run("MarshalJSON()", func(t *testing.T) { + assert.Equal(t, map[string]interface{}{ + "redis": "1.0.0", + }, testOptionsSerialize(t, o)) }) } diff --git a/timestamp_test.go b/timestamp_test.go index a3d3511..7907b0a 100644 --- a/timestamp_test.go +++ b/timestamp_test.go @@ -1,12 +1,10 @@ package sentry import ( - "encoding/json" - "fmt" "testing" "time" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleTimestamp() { @@ -19,27 +17,15 @@ func ExampleTimestamp() { } func TestTimestamp(t *testing.T) { - Convey("Timestamp", t, func() { - Convey("Should register itself with the default providers", func() { - opt := testGetOptionsProvider(Timestamp(time.Now())) - So(opt, ShouldNotBeNil) - }) + assert.NotNil(t, testGetOptionsProvider(t, Timestamp(time.Now())), "it should be registered as a default option") - Convey("Timestamp()", func() { - Convey("Should use the correct Class()", func() { - So(Timestamp(time.Now()).Class(), ShouldEqual, "timestamp") - }) + now := time.Now() + o := Timestamp(now) + assert.NotNil(t, o, "should not return a nil option") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "timestamp", o.Class(), "it should use the right option class") - Convey("MarshalJSON", func() { - Convey("Should marshal to a string", func() { - Convey("Should marshal to a string", func() { - t := time.Now() - b, err := json.Marshal(Timestamp(t)) - So(err, ShouldBeNil) - So(string(b), ShouldEqual, fmt.Sprintf(`"%s"`, t.UTC().Format("2006-01-02T15:04:05"))) - }) - }) - }) - }) + t.Run("MarshalJSON()", func(t *testing.T) { + assert.Equal(t, now.UTC().Format("2006-01-02T15:04:05"), testOptionsSerialize(t, o), "it should serialize to a string") }) } diff --git a/transport_test.go b/transport_test.go index b35e97b..333949a 100644 --- a/transport_test.go +++ b/transport_test.go @@ -3,7 +3,7 @@ package sentry import ( "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleUseTransport() { @@ -21,30 +21,18 @@ func ExampleUseTransport() { } func TestTransport(t *testing.T) { - Convey("Transport", t, func() { - Convey("UseTransport()", func() { - Convey("Should return an Option", func() { - t := newHTTPTransport() - So(UseTransport(t), ShouldImplement, (*Option)(nil)) - }) - - Convey("Should return nil if no queue is provided", func() { - So(UseTransport(nil), ShouldEqual, nil) - }) - - Convey("Should use the correct Class()", func() { - t := newHTTPTransport() - So(UseTransport(t).Class(), ShouldEqual, "sentry-go.transport") - }) - - Convey("Should implement Omit() and always return true", func() { - t := newHTTPTransport() - o := UseTransport(t) - So(o, ShouldImplement, (*OmitableOption)(nil)) - So(o.(OmitableOption).Omit(), ShouldBeTrue) - }) - }) - }) + assert.Nil(t, UseTransport(nil), "it should return nil if no transport is provided") + + tr := newHTTPTransport() + o := UseTransport(tr) + assert.NotNil(t, o, "should not return a nil option") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "sentry-go.transport", o.Class(), "it should use the right option class") + + if assert.Implements(t, (*Option)(nil), o, "it should implement the OmitableOption interface") { + oo := o.(OmitableOption) + assert.True(t, oo.Omit(), "it should always return true for calls to Omit()") + } } func testNewTestTransport() *testTransport { diff --git a/unset_test.go b/unset_test.go index 53644cc..d087b77 100644 --- a/unset_test.go +++ b/unset_test.go @@ -1,10 +1,9 @@ package sentry import ( - "encoding/json" "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func ExampleUnset() { @@ -21,32 +20,23 @@ func ExampleUnset() { } func TestUnset(t *testing.T) { - Convey("Unset", t, func() { - Convey("Unset()", func() { - Convey("Should use the correct Class()", func() { - So(Unset("runtime").Class(), ShouldEqual, "runtime") - So(Unset("device").Class(), ShouldEqual, "device") - }) - - Convey("MarshalJSON", func() { - Convey("Should marshal to null", func() { - b, err := json.Marshal(Unset("runtime")) - So(err, ShouldBeNil) - So(string(b), ShouldEqual, `null`) - }) - }) - }) - - Convey("Should implement the advanced option interface", func() { - So(Unset("runtime"), ShouldImplement, (*AdvancedOption)(nil)) - }) - - Convey("Should correctly unset fields from the packet", func() { - p := map[string]Option{} - p["level"] = Level(Error) - Unset("level").(AdvancedOption).Apply(p) - - So(p, ShouldResemble, map[string]Option{}) - }) + o := Unset("runtime") + assert.Equal(t, o.Class(), "runtime", "it should use the correct class name") + + o = Unset("device") + assert.Equal(t, o.Class(), "device", "it should use the correct class name") + + t.Run("MarshalJSON()", func(t *testing.T) { + assert.Equal(t, nil, testOptionsSerialize(t, o), "it should serialize to nil") }) + + if assert.Implements(t, (*AdvancedOption)(nil), o, "it should implement the AdvancedOption interface") { + p := map[string]Option{ + "level": Level(Error), + "release": Release("1.0.0"), + } + + Unset("level").(AdvancedOption).Apply(p) + assert.NotContains(t, "level", p, "it should remove the property from the packet") + } } diff --git a/user_test.go b/user_test.go index 353d193..aec16f0 100644 --- a/user_test.go +++ b/user_test.go @@ -3,7 +3,8 @@ package sentry import ( "testing" - . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func ExampleUser() { @@ -29,59 +30,32 @@ func ExampleUser() { } func TestUser(t *testing.T) { - Convey("User", t, func() { - user := UserInfo{ - ID: "17ba08f7cc89a912bf812918", - Email: "test@example.com", - Username: "Test User", - IPAddress: "127.0.0.1", - Extra: map[string]string{ - "role": "Tester", - }, - } + assert.Nil(t, User(nil), "it should return nil if the user details are nil") - fields := map[string]string{ - "id": "17ba08f7cc89a912bf812918", - "email": "test@example.com", - "username": "Test User", - "ip_address": "127.0.0.1", - "role": "Tester", - } - - Convey("User()", func() { - Convey("Should return an Option", func() { - So(User(&user), ShouldImplement, (*Option)(nil)) - }) - - Convey("Should return nil if the user info is nil", func() { - So(User(nil), ShouldBeNil) - }) - - Convey("Should use the correct Class()", func() { - So(User(&user).Class(), ShouldEqual, "user") - }) - - Convey("Should have the correct fields set", func() { - u := User(&user) - So(u, ShouldNotBeNil) - - ui, ok := u.(*userOption) - So(ok, ShouldBeTrue) - - So(ui.fields, ShouldResemble, fields) - }) + user := UserInfo{ + ID: "17ba08f7cc89a912bf812918", + Email: "test@example.com", + Username: "Test User", + IPAddress: "127.0.0.1", + Extra: map[string]string{ + "role": "Tester", + }, + } - Convey("MarshalJSON", func() { - u := User(&user) - So(u, ShouldNotBeNil) + fields := map[string]interface{}{ + "id": "17ba08f7cc89a912bf812918", + "email": "test@example.com", + "username": "Test User", + "ip_address": "127.0.0.1", + "role": "Tester", + } - expected := map[string]interface{}{} - for k, v := range fields { - expected[k] = v - } + o := User(&user) + require.NotNil(t, o, "should not return a nil option") + assert.Implements(t, (*Option)(nil), o, "it should implement the Option interface") + assert.Equal(t, "user", o.Class(), "it should use the right option class") - So(testOptionsSerialize(u), ShouldResemble, expected) - }) - }) + t.Run("MarshalJSON()", func(t *testing.T) { + assert.Equal(t, fields, testOptionsSerialize(t, o), "it should serialize to the right fields") }) }