diff --git a/block_test.go b/block_test.go index b6bc548..6122628 100644 --- a/block_test.go +++ b/block_test.go @@ -3,6 +3,7 @@ package notionapi_test import ( "context" "github.com/jomei/notionapi" + "net/http" "reflect" "testing" "time" @@ -15,24 +16,26 @@ func TestBlockClient(t *testing.T) { } t.Run("GetChildren", func(t *testing.T) { tests := []struct { - name string - filePath string - id notionapi.BlockID - len int - wantErr bool - err error + name string + filePath string + statusCode int + id notionapi.BlockID + len int + wantErr bool + err error }{ { - name: "returns blocks by id of parent block", - id: "some_id", - filePath: "testdata/block_get_children.json", - len: 2, + name: "returns blocks by id of parent block", + id: "some_id", + statusCode: http.StatusOK, + filePath: "testdata/block_get_children.json", + len: 2, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := newMockedClient(t, tt.filePath) + c := newMockedClient(t, tt.filePath, tt.statusCode) client := notionapi.NewClient("some_token", notionapi.WithHTTPClient(c)) got, err := client.Block.GetChildren(context.Background(), tt.id, nil) @@ -50,18 +53,20 @@ func TestBlockClient(t *testing.T) { t.Run("AppendChildren", func(t *testing.T) { tests := []struct { - name string - filePath string - id notionapi.BlockID - request *notionapi.AppendBlockChildrenRequest - want *notionapi.ChildPageBlock - wantErr bool - err error + name string + filePath string + statusCode int + id notionapi.BlockID + request *notionapi.AppendBlockChildrenRequest + want *notionapi.ChildPageBlock + wantErr bool + err error }{ { - name: "returns blocks by id of parent block", - id: "some_id", - filePath: "testdata/block_append_children.json", + name: "returns blocks by id of parent block", + id: "some_id", + filePath: "testdata/block_append_children.json", + statusCode: http.StatusOK, request: ¬ionapi.AppendBlockChildrenRequest{ Children: []notionapi.Block{ ¬ionapi.Heading2Block{ @@ -96,7 +101,7 @@ func TestBlockClient(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := newMockedClient(t, tt.filePath) + c := newMockedClient(t, tt.filePath, tt.statusCode) client := notionapi.NewClient("some_token", notionapi.WithHTTPClient(c)) got, err := client.Block.AppendChildren(context.Background(), tt.id, tt.request) diff --git a/client.go b/client.go index 0bb4902..c8eda86 100644 --- a/client.go +++ b/client.go @@ -5,7 +5,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/pkg/errors" "io" "net/http" "net/url" @@ -119,7 +118,13 @@ func (c *Client) request(ctx context.Context, method string, urlStr string, quer } if res.StatusCode != http.StatusOK { - return nil, errors.Errorf("http status: %d", res.StatusCode) + var apiErr Error + err = json.NewDecoder(res.Body).Decode(&apiErr) + if err != nil { + return nil, err + } + + return nil, &apiErr } return res, nil diff --git a/client_test.go b/client_test.go index e62a0c9..dc96738 100644 --- a/client_test.go +++ b/client_test.go @@ -22,7 +22,7 @@ func newTestClient(fn RoundTripFunc) *http.Client { } // newMockedClient returns *http.Client which responds with content from given file -func newMockedClient(t *testing.T, requestMockFile string) *http.Client { +func newMockedClient(t *testing.T, requestMockFile string, statusCode int) *http.Client { return newTestClient(func(req *http.Request) *http.Response { b, err := os.Open(requestMockFile) if err != nil { @@ -30,7 +30,7 @@ func newMockedClient(t *testing.T, requestMockFile string) *http.Client { } resp := &http.Response{ - StatusCode: http.StatusOK, + StatusCode: statusCode, Body: b, Header: make(http.Header), } diff --git a/const.go b/const.go index 6e0c3c8..d0828a4 100644 --- a/const.go +++ b/const.go @@ -7,6 +7,7 @@ const ( ObjectTypeList ObjectType = "list" ObjectTypeText ObjectType = "text" ObjectTypeUser ObjectType = "user" + ObjectTypeError ObjectType = "error" ) const ( diff --git a/database_test.go b/database_test.go index 13d4138..7969720 100644 --- a/database_test.go +++ b/database_test.go @@ -3,6 +3,7 @@ package notionapi_test import ( "context" "github.com/jomei/notionapi" + "net/http" "reflect" "testing" "time" @@ -16,17 +17,19 @@ func TestDatabaseClient(t *testing.T) { t.Run("Get", func(t *testing.T) { tests := []struct { - name string - filePath string - id notionapi.DatabaseID - want *notionapi.Database - wantErr bool - err error + name string + filePath string + statusCode int + id notionapi.DatabaseID + want *notionapi.Database + wantErr bool + err error }{ { - name: "returns database by id", - id: "some_id", - filePath: "testdata/database_get.json", + name: "returns database by id", + id: "some_id", + filePath: "testdata/database_get.json", + statusCode: http.StatusOK, want: ¬ionapi.Database{ Object: notionapi.ObjectTypeDatabase, ID: "some_id", @@ -69,8 +72,7 @@ func TestDatabaseClient(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - - c := newMockedClient(t, tt.filePath) + c := newMockedClient(t, tt.filePath, tt.statusCode) client := notionapi.NewClient("some_token", notionapi.WithHTTPClient(c)) got, err := client.Database.Get(context.Background(), tt.id) @@ -89,15 +91,17 @@ func TestDatabaseClient(t *testing.T) { t.Run("List", func(t *testing.T) { tests := []struct { - name string - filePath string - want *notionapi.DatabaseListResponse - wantErr bool - err error + name string + filePath string + statusCode int + want *notionapi.DatabaseListResponse + wantErr bool + err error }{ { - name: "returns list of databases", - filePath: "testdata/database_list.json", + name: "returns list of databases", + filePath: "testdata/database_list.json", + statusCode: http.StatusOK, want: ¬ionapi.DatabaseListResponse{ Object: notionapi.ObjectTypeList, Results: []notionapi.Database{ @@ -128,7 +132,7 @@ func TestDatabaseClient(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := newMockedClient(t, tt.filePath) + c := newMockedClient(t, tt.filePath, tt.statusCode) client := notionapi.NewClient("some_token", notionapi.WithHTTPClient(c)) got, err := client.Database.List(context.Background(), nil) @@ -147,18 +151,20 @@ func TestDatabaseClient(t *testing.T) { t.Run("Query", func(t *testing.T) { tests := []struct { - name string - filePath string - id notionapi.DatabaseID - request *notionapi.DatabaseQueryRequest - want *notionapi.DatabaseQueryResponse - wantErr bool - err error + name string + filePath string + statusCode int + id notionapi.DatabaseID + request *notionapi.DatabaseQueryRequest + want *notionapi.DatabaseQueryResponse + wantErr bool + err error }{ { - name: "returns query results", - id: "some_id", - filePath: "testdata/database_query.json", + name: "returns query results", + id: "some_id", + filePath: "testdata/database_query.json", + statusCode: http.StatusOK, request: ¬ionapi.DatabaseQueryRequest{ Filter: ¬ionapi.PropertyFilter{ Property: "Name", @@ -190,7 +196,7 @@ func TestDatabaseClient(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := newMockedClient(t, tt.filePath) + c := newMockedClient(t, tt.filePath, tt.statusCode) client := notionapi.NewClient("some_token", notionapi.WithHTTPClient(c)) got, err := client.Database.Query(context.Background(), tt.id, tt.request) diff --git a/error.go b/error.go new file mode 100644 index 0000000..15fdf53 --- /dev/null +++ b/error.go @@ -0,0 +1,14 @@ +package notionapi + +type ErrorCode string + +type Error struct { + Object ObjectType `json:"object"` + Status int `json:"status"` + Code ErrorCode `json:"code"` + Message string `json:"message"` +} + +func (e *Error) Error() string { + return e.Message +} diff --git a/page_test.go b/page_test.go index 47e6d98..cc47e47 100644 --- a/page_test.go +++ b/page_test.go @@ -3,6 +3,7 @@ package notionapi_test import ( "context" "github.com/jomei/notionapi" + "net/http" "reflect" "testing" "time" @@ -16,17 +17,19 @@ func TestPageClient(t *testing.T) { t.Run("Get", func(t *testing.T) { tests := []struct { - name string - filePath string - id notionapi.PageID - want *notionapi.Page - wantErr bool - err error + name string + filePath string + statusCode int + id notionapi.PageID + want *notionapi.Page + wantErr bool + err error }{ { - name: "returns page by id", - id: "some_id", - filePath: "testdata/page_get.json", + name: "returns page by id", + id: "some_id", + filePath: "testdata/page_get.json", + statusCode: http.StatusOK, want: ¬ionapi.Page{ Object: notionapi.ObjectTypePage, ID: "some_id", @@ -39,16 +42,36 @@ func TestPageClient(t *testing.T) { Archived: false, }, }, + { + name: "returns validation error for invalid request", + id: "some_id", + filePath: "testdata/validation_error.json", + statusCode: http.StatusBadRequest, + wantErr: true, + err: ¬ionapi.Error{ + Object: notionapi.ObjectTypeError, + Status: http.StatusBadRequest, + Code: "validation_error", + Message: "The provided page ID is not a valid Notion UUID: bla bla.", + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := newMockedClient(t, tt.filePath) + c := newMockedClient(t, tt.filePath, tt.statusCode) client := notionapi.NewClient("some_token", notionapi.WithHTTPClient(c)) got, err := client.Page.Get(context.Background(), tt.id) - if (err != nil) != tt.wantErr { - t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr) + if err != nil { + if tt.wantErr { + if !reflect.DeepEqual(err, tt.err) { + t.Errorf("Get error() got = %v, want %v", err, tt.err) + } + } else { + t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr) + + } return } // TODO: remove properties from comparing for a while. Have to compare with interface somehow @@ -62,17 +85,19 @@ func TestPageClient(t *testing.T) { t.Run("Create", func(t *testing.T) { tests := []struct { - name string - filePath string - id notionapi.PageID - request *notionapi.PageCreateRequest - want *notionapi.Page - wantErr bool - err error + name string + filePath string + statusCode int + id notionapi.PageID + request *notionapi.PageCreateRequest + want *notionapi.Page + wantErr bool + err error }{ { - name: "returns a new page", - filePath: "testdata/page_create.json", + name: "returns a new page", + filePath: "testdata/page_create.json", + statusCode: http.StatusOK, request: ¬ionapi.PageCreateRequest{ Parent: notionapi.Parent{ Type: notionapi.ParentTypeDatabaseID, @@ -102,7 +127,7 @@ func TestPageClient(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := newMockedClient(t, tt.filePath) + c := newMockedClient(t, tt.filePath, tt.statusCode) client := notionapi.NewClient("some_token", notionapi.WithHTTPClient(c)) got, err := client.Page.Create(context.Background(), tt.request) if (err != nil) != tt.wantErr { @@ -120,18 +145,20 @@ func TestPageClient(t *testing.T) { t.Run("Update", func(t *testing.T) { tests := []struct { - name string - filePath string - id notionapi.PageID - request *notionapi.PageUpdateRequest - want *notionapi.Page - wantErr bool - err error + name string + filePath string + statusCode int + id notionapi.PageID + request *notionapi.PageUpdateRequest + want *notionapi.Page + wantErr bool + err error }{ { - name: "change requested properties and return the result", - id: "some_id", - filePath: "testdata/page_update.json", + name: "change requested properties and return the result", + id: "some_id", + filePath: "testdata/page_update.json", + statusCode: http.StatusOK, request: ¬ionapi.PageUpdateRequest{ Properties: notionapi.Properties{ "SomeColumn": notionapi.RichTextProperty{ @@ -160,7 +187,7 @@ func TestPageClient(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := newMockedClient(t, tt.filePath) + c := newMockedClient(t, tt.filePath, tt.statusCode) client := notionapi.NewClient("some_token", notionapi.WithHTTPClient(c)) got, err := client.Page.Update(context.Background(), tt.id, tt.request) if (err != nil) != tt.wantErr { diff --git a/search_test.go b/search_test.go index 523c187..94f4313 100644 --- a/search_test.go +++ b/search_test.go @@ -3,21 +3,24 @@ package notionapi_test import ( "context" "github.com/jomei/notionapi" + "net/http" "testing" ) func TestSearchClient(t *testing.T) { t.Run("Do", func(t *testing.T) { tests := []struct { - name string - filePath string - request *notionapi.SearchRequest - wantErr bool - err error + name string + filePath string + statusCode int + request *notionapi.SearchRequest + wantErr bool + err error }{ { - name: "returns search result", - filePath: "testdata/search.json", + name: "returns search result", + filePath: "testdata/search.json", + statusCode: http.StatusOK, request: ¬ionapi.SearchRequest{ Query: "Hel", }, @@ -26,7 +29,7 @@ func TestSearchClient(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := newMockedClient(t, tt.filePath) + c := newMockedClient(t, tt.filePath, tt.statusCode) client := notionapi.NewClient("some_token", notionapi.WithHTTPClient(c)) got, err := client.Search.Do(context.Background(), tt.request) diff --git a/testdata/validation_error.json b/testdata/validation_error.json new file mode 100644 index 0000000..3b6cfa7 --- /dev/null +++ b/testdata/validation_error.json @@ -0,0 +1,6 @@ +{ + "object": "error", + "status": 400, + "code": "validation_error", + "message": "The provided page ID is not a valid Notion UUID: bla bla." +} diff --git a/user_test.go b/user_test.go index a8fc5f3..e5b27a0 100644 --- a/user_test.go +++ b/user_test.go @@ -3,6 +3,7 @@ package notionapi_test import ( "context" "github.com/jomei/notionapi" + "net/http" "reflect" "testing" ) @@ -10,17 +11,19 @@ import ( func TestUserClient(t *testing.T) { t.Run("Get", func(t *testing.T) { tests := []struct { - name string - filePath string - id notionapi.UserID - want *notionapi.User - wantErr bool - err error + name string + filePath string + statusCode int + id notionapi.UserID + want *notionapi.User + wantErr bool + err error }{ { - name: "returns user by id", - id: "some_id", - filePath: "testdata/user_get.json", + name: "returns user by id", + id: "some_id", + filePath: "testdata/user_get.json", + statusCode: http.StatusOK, want: ¬ionapi.User{ Object: notionapi.ObjectTypeUser, ID: "some_id", @@ -34,7 +37,7 @@ func TestUserClient(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := newMockedClient(t, tt.filePath) + c := newMockedClient(t, tt.filePath, tt.statusCode) client := notionapi.NewClient("some_token", notionapi.WithHTTPClient(c)) got, err := client.User.Get(context.Background(), tt.id) @@ -52,15 +55,17 @@ func TestUserClient(t *testing.T) { t.Run("List", func(t *testing.T) { tests := []struct { - name string - filePath string - want *notionapi.UsersListResponse - wantErr bool - err error + name string + filePath string + statusCode int + want *notionapi.UsersListResponse + wantErr bool + err error }{ { - name: "returns list of users", - filePath: "testdata/user_list.json", + name: "returns list of users", + filePath: "testdata/user_list.json", + statusCode: http.StatusOK, want: ¬ionapi.UsersListResponse{ Object: notionapi.ObjectTypeList, Results: []notionapi.User{ @@ -87,7 +92,7 @@ func TestUserClient(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := newMockedClient(t, tt.filePath) + c := newMockedClient(t, tt.filePath, tt.statusCode) client := notionapi.NewClient("some_token", notionapi.WithHTTPClient(c)) got, err := client.User.List(context.Background(), nil) if (err != nil) != tt.wantErr {