1
0
mirror of synced 2024-11-22 04:56:06 +03:00

refactoring

This commit is contained in:
Ruslan Efanov 2022-10-26 10:54:54 +03:00
parent fa11f4ffd3
commit bf7e5cdb93
7 changed files with 275 additions and 186 deletions

1
go.sum
View File

@ -15,7 +15,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@ -3,6 +3,7 @@ package v1
import ( import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"strings" "strings"
@ -45,61 +46,65 @@ func (t *MGClientTest) Test_TransportChannels() {
t.gock(). t.gock().
Get(t.transportURL("channels")). Get(t.transportURL("channels")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON([]ChannelListItem{{ JSON(
ID: 1, []ChannelListItem{
ExternalID: "external_id", {
Type: "whatsapp", ID: 1,
Name: &chName, ExternalID: "external_id",
Settings: ChannelSettings{ Type: "whatsapp",
Status: Status{ Name: &chName,
Delivered: ChannelFeatureNone, Settings: ChannelSettings{
Read: ChannelFeatureSend, Status: Status{
}, Delivered: ChannelFeatureNone,
Text: ChannelSettingsText{ Read: ChannelFeatureSend,
Creating: ChannelFeatureBoth, },
Editing: ChannelFeatureBoth, Text: ChannelSettingsText{
Quoting: ChannelFeatureBoth, Creating: ChannelFeatureBoth,
Deleting: ChannelFeatureReceive, Editing: ChannelFeatureBoth,
MaxCharsCount: 4096, Quoting: ChannelFeatureBoth,
}, Deleting: ChannelFeatureReceive,
Product: Product{ MaxCharsCount: 4096,
Creating: ChannelFeatureReceive, },
Editing: ChannelFeatureReceive, Product: Product{
}, Creating: ChannelFeatureReceive,
Order: Order{ Editing: ChannelFeatureReceive,
Creating: ChannelFeatureReceive, },
Editing: ChannelFeatureReceive, Order: Order{
}, Creating: ChannelFeatureReceive,
File: ChannelSettingsFilesBase{ Editing: ChannelFeatureReceive,
Creating: ChannelFeatureBoth, },
Editing: ChannelFeatureBoth, File: ChannelSettingsFilesBase{
Quoting: ChannelFeatureBoth, Creating: ChannelFeatureBoth,
Deleting: ChannelFeatureReceive, Editing: ChannelFeatureBoth,
Max: 1, Quoting: ChannelFeatureBoth,
}, Deleting: ChannelFeatureReceive,
Image: ChannelSettingsFilesBase{ Max: 1,
Creating: ChannelFeatureBoth, },
Editing: ChannelFeatureBoth, Image: ChannelSettingsFilesBase{
Quoting: ChannelFeatureBoth, Creating: ChannelFeatureBoth,
Deleting: ChannelFeatureReceive, Editing: ChannelFeatureBoth,
Max: 1, // nolint:gomnd Quoting: ChannelFeatureBoth,
}, Deleting: ChannelFeatureReceive,
Suggestions: ChannelSettingsSuggestions{ Max: 1, // nolint:gomnd
Text: ChannelFeatureBoth, },
Phone: ChannelFeatureBoth, Suggestions: ChannelSettingsSuggestions{
Email: ChannelFeatureBoth, Text: ChannelFeatureBoth,
}, Phone: ChannelFeatureBoth,
CustomerExternalID: ChannelFeatureCustomerExternalIDPhone, Email: ChannelFeatureBoth,
SendingPolicy: SendingPolicy{ },
NewCustomer: ChannelFeatureSendingPolicyTemplate, CustomerExternalID: ChannelFeatureCustomerExternalIDPhone,
SendingPolicy: SendingPolicy{
NewCustomer: ChannelFeatureSendingPolicyTemplate,
},
},
CreatedAt: createdAt,
UpdatedAt: &createdAt,
ActivatedAt: createdAt,
DeactivatedAt: nil,
IsActive: true,
}, },
}, },
CreatedAt: createdAt, )
UpdatedAt: &createdAt,
ActivatedAt: createdAt,
DeactivatedAt: nil,
IsActive: true,
}})
data, status, err := c.TransportChannels(Channels{Active: true}) data, status, err := c.TransportChannels(Channels{Active: true})
t.Require().NoError(err) t.Require().NoError(err)
@ -147,11 +152,13 @@ func (t *MGClientTest) Test_ActivateTransportChannel() {
t.gock(). t.gock().
Post(t.transportURL("channels")). Post(t.transportURL("channels")).
Reply(http.StatusCreated). Reply(http.StatusCreated).
JSON(ActivateResponse{ JSON(
ChannelID: 1, ActivateResponse{
ExternalID: "external_id_1", ChannelID: 1,
ActivatedAt: time.Now(), ExternalID: "external_id_1",
}) ActivatedAt: time.Now(),
},
)
data, status, err := c.ActivateTransportChannel(ch) data, status, err := c.ActivateTransportChannel(ch)
t.Require().NoError(err) t.Require().NoError(err)
@ -200,19 +207,23 @@ func (t *MGClientTest) Test_ActivateNewTransportChannel() {
t.gock(). t.gock().
Post(t.transportURL("channels")). Post(t.transportURL("channels")).
Reply(http.StatusCreated). Reply(http.StatusCreated).
JSON(ActivateResponse{ JSON(
ChannelID: 1, ActivateResponse{
ExternalID: "external_id_1", ChannelID: 1,
ActivatedAt: time.Now(), ExternalID: "external_id_1",
}) ActivatedAt: time.Now(),
},
)
t.gock(). t.gock().
Delete(t.transportURL("channels/1")). Delete(t.transportURL("channels/1")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(DeleteResponse{ JSON(
ChannelID: 1, DeleteResponse{
DeactivatedAt: time.Now(), ChannelID: 1,
}) DeactivatedAt: time.Now(),
},
)
data, status, err := c.ActivateTransportChannel(ch) data, status, err := c.ActivateTransportChannel(ch)
t.Require().NoError(err) t.Require().NoError(err)
@ -266,11 +277,13 @@ func (t *MGClientTest) Test_UpdateTransportChannel() {
t.gock(). t.gock().
Put(t.transportURL("channels/1")). Put(t.transportURL("channels/1")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(UpdateResponse{ JSON(
ChannelID: uint64(1), UpdateResponse{
ExternalID: "external_id_1", ChannelID: uint64(1),
UpdatedAt: time.Now(), ExternalID: "external_id_1",
}) UpdatedAt: time.Now(),
},
)
data, status, err := c.UpdateTransportChannel(ch) data, status, err := c.UpdateTransportChannel(ch)
t.Require().NoError(err) t.Require().NoError(err)
@ -287,27 +300,31 @@ func (t *MGClientTest) Test_TransportTemplates() {
t.gock(). t.gock().
Get(t.transportURL("templates")). Get(t.transportURL("templates")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON([]Template{{ JSON(
Code: "tpl_code", []Template{
ChannelID: 1,
Name: "Test Template",
Enabled: true,
Type: TemplateTypeText,
Template: []TemplateItem{
{ {
Type: TemplateItemTypeText, Code: "tpl_code",
Text: "Hello, ", ChannelID: 1,
}, Name: "Test Template",
{ Enabled: true,
Type: TemplateItemTypeVar, Type: TemplateTypeText,
VarType: TemplateVarFirstName, Template: []TemplateItem{
}, {
{ Type: TemplateItemTypeText,
Type: TemplateItemTypeText, Text: "Hello, ",
Text: "! We're glad to see you back in our store.", },
{
Type: TemplateItemTypeVar,
VarType: TemplateVarFirstName,
},
{
Type: TemplateItemTypeText,
Text: "! We're glad to see you back in our store.",
},
},
}, },
}, },
}}) )
data, status, err := c.TransportTemplates() data, status, err := c.TransportTemplates()
t.Assert().NoError(err, fmt.Sprintf("%d %s", status, err)) t.Assert().NoError(err, fmt.Sprintf("%d %s", status, err))
@ -390,10 +407,12 @@ func (t *MGClientTest) Test_UpdateTemplate() {
defer gock.Off() defer gock.Off()
t.gock(). t.gock().
Filter(func(r *http.Request) bool { Filter(
return r.Method == http.MethodPut && func(r *http.Request) bool {
r.URL.Path == "/api/transport/v1/channels/1/templates/encodable#code" return r.Method == http.MethodPut &&
}). r.URL.Path == "/api/transport/v1/channels/1/templates/encodable#code"
},
).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(map[string]interface{}{}) JSON(map[string]interface{}{})
@ -440,9 +459,11 @@ func (t *MGClientTest) Test_UpdateTemplateFail() {
defer gock.Off() defer gock.Off()
t.gock(). t.gock().
Reply(http.StatusBadRequest). Reply(http.StatusBadRequest).
JSON(map[string][]string{ JSON(
"errors": {"Some weird error message..."}, map[string][]string{
}) "errors": {"Some weird error message..."},
},
)
status, err := c.UpdateTemplate(tpl) status, err := c.UpdateTemplate(tpl)
t.Assert().Error(err, fmt.Sprintf("%d %s", status, err)) t.Assert().Error(err, fmt.Sprintf("%d %s", status, err))
@ -453,10 +474,12 @@ func (t *MGClientTest) Test_DeactivateTemplate() {
defer gock.Off() defer gock.Off()
t.gock(). t.gock().
Filter(func(r *http.Request) bool { Filter(
return r.Method == http.MethodDelete && func(r *http.Request) bool {
r.URL.Path == t.transportURL("channels/1/templates/test_template#code") return r.Method == http.MethodDelete &&
}). r.URL.Path == t.transportURL("channels/1/templates/test_template#code")
},
).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(map[string]interface{}{}) JSON(map[string]interface{}{})
@ -487,10 +510,12 @@ func (t *MGClientTest) Test_TextMessages() {
t.gock(). t.gock().
Post(t.transportURL("messages")). Post(t.transportURL("messages")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(MessagesResponse{ JSON(
MessageID: 1, MessagesResponse{
Time: time.Now(), MessageID: 1,
}) Time: time.Now(),
},
)
data, status, err := c.Messages(snd) data, status, err := c.Messages(snd)
t.Require().NoError(err) t.Require().NoError(err)
@ -507,26 +532,32 @@ func (t *MGClientTest) Test_ImageMessages() {
t.gock(). t.gock().
Post(t.transportURL("files/upload_by_url")). Post(t.transportURL("files/upload_by_url")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(UploadFileResponse{ JSON(
ID: "1", UploadFileResponse{
Hash: "1", ID: "1",
Type: "image/png", Hash: "1",
MimeType: "", Type: "image/png",
Size: 1024, MimeType: "",
CreatedAt: time.Now(), Size: 1024,
}) CreatedAt: time.Now(),
},
)
t.gock(). t.gock().
Post(t.transportURL("messages")). Post(t.transportURL("messages")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(MessagesResponse{ JSON(
MessageID: 1, MessagesResponse{
Time: time.Now(), MessageID: 1,
}) Time: time.Now(),
},
)
uploadFileResponse, st, err := c.UploadFileByURL(UploadFileByUrlRequest{ uploadFileResponse, st, err := c.UploadFileByURL(
Url: "https://via.placeholder.com/1", UploadFileByUrlRequest{
}) Url: "https://via.placeholder.com/1",
},
)
t.Require().NoError(err) t.Require().NoError(err)
t.Assert().Equal(http.StatusOK, st) t.Assert().Equal(http.StatusOK, st)
t.Assert().Equal("1", uploadFileResponse.ID) t.Assert().Equal("1", uploadFileResponse.ID)
@ -569,10 +600,12 @@ func (t *MGClientTest) Test_UpdateMessages() {
t.gock(). t.gock().
Put(t.transportURL("messages")). Put(t.transportURL("messages")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(MessagesResponse{ JSON(
MessageID: 1, MessagesResponse{
Time: time.Now(), MessageID: 1,
}) Time: time.Now(),
},
)
dataU, status, err := c.UpdateMessages(sndU) dataU, status, err := c.UpdateMessages(sndU)
t.Require().NoError(err) t.Require().NoError(err)
@ -599,28 +632,34 @@ func (t *MGClientTest) Test_MarkMessageReadAndDelete() {
t.gock(). t.gock().
Delete(t.transportURL("messages")). Delete(t.transportURL("messages")).
JSON(DeleteData{ JSON(
Message: Message{ DeleteData{
ExternalID: "deleted", Message: Message{
ExternalID: "deleted",
},
Channel: 1,
}, },
Channel: 1, ).
}).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(MessagesResponse{ JSON(
MessageID: 2, MessagesResponse{
Time: time.Now(), MessageID: 2,
}) Time: time.Now(),
},
)
_, status, err := c.MarkMessageRead(snd) _, status, err := c.MarkMessageRead(snd)
t.Require().NoError(err) t.Require().NoError(err)
t.Assert().Equal(http.StatusOK, status) t.Assert().Equal(http.StatusOK, status)
previousChatMessage, status, err := c.DeleteMessage(DeleteData{ previousChatMessage, status, err := c.DeleteMessage(
Message{ DeleteData{
ExternalID: "deleted", Message{
ExternalID: "deleted",
},
1,
}, },
1, )
})
t.Require().NoError(err) t.Require().NoError(err)
t.Assert().Equal(http.StatusOK, status) t.Assert().Equal(http.StatusOK, status)
t.Assert().Equal(2, previousChatMessage.MessageID) t.Assert().Equal(2, previousChatMessage.MessageID)
@ -633,10 +672,12 @@ func (t *MGClientTest) Test_DeactivateTransportChannel() {
t.gock(). t.gock().
Delete(t.transportURL("channels/1")). Delete(t.transportURL("channels/1")).
Reply(http.StatusOK). Reply(http.StatusOK).
JSON(DeleteResponse{ JSON(
ChannelID: 1, DeleteResponse{
DeactivatedAt: time.Now(), ChannelID: 1,
}) DeactivatedAt: time.Now(),
},
)
deleteData, status, err := c.DeactivateTransportChannel(1) deleteData, status, err := c.DeactivateTransportChannel(1)
t.Require().NoError(err) t.Require().NoError(err)
@ -679,9 +720,10 @@ func (t *MGClientTest) Test_UploadFile() {
t.Assert().Equal(resp, data) t.Assert().Equal(resp, data)
} }
func (t *MGClientTest) Test_SuccessHandleAPIError() { func (t *MGClientTest) Test_SuccessHandleError() {
client := t.client() client := t.client()
handleError := client.Error([]byte(`{"errors": ["Channel not found"]}`)) json := `{"errors": ["Channel not found"]}`
handleError := client.Error([]byte(json))
t.Assert().IsType(new(httpClientError), handleError) t.Assert().IsType(new(httpClientError), handleError)
t.Assert().Equal(handleError.Error(), "Channel not found") t.Assert().Equal(handleError.Error(), "Channel not found")
@ -691,9 +733,25 @@ func (t *MGClientTest) Test_SuccessHandleAPIError() {
Delete(t.transportURL("channels/123")). Delete(t.transportURL("channels/123")).
Reply(http.StatusInternalServerError) Reply(http.StatusInternalServerError)
t.gock().
Delete(t.transportURL("channels/455")).
Reply(http.StatusBadRequest).
JSON(json)
_, statusCode, err := client.DeactivateTransportChannel(123) _, statusCode, err := client.DeactivateTransportChannel(123)
t.Assert().Equal(http.StatusInternalServerError, statusCode) t.Assert().Equal(http.StatusInternalServerError, statusCode)
t.Assert().IsType(new(httpClientError), err) t.Assert().IsType(new(httpClientError), err)
t.Assert().Equal("Internal server error", err.Error()) t.Assert().Equal("Internal server error", err.Error())
var serverErr *httpClientError
if errors.As(err, &serverErr) {
t.Assert().NotNil(serverErr.ResponseBody)
} else {
t.Fail("Unexpected type of error")
}
_, statusCode, err = client.DeactivateTransportChannel(455)
t.Assert().Equal(http.StatusBadRequest, statusCode)
t.Assert().IsType(new(httpClientError), err)
t.Assert().Equal("Channel not found", err.Error())
} }

View File

@ -2,14 +2,19 @@ package v1
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt"
"io"
"net/http"
) )
var defaultErrorMessage = "Internal http client error" var defaultErrorMessage = "Internal http client error"
var internalServerError = "Internal server error" var internalServerError = "Internal server error"
type httpClientError struct { type httpClientError struct {
ErrorMsg string ErrorMsg string
BaseError error BaseError error
ResponseBody io.ReadCloser
} }
func (err *httpClientError) Unwrap() error { func (err *httpClientError) Unwrap() error {
@ -20,10 +25,10 @@ func (err *httpClientError) Error() string {
message := defaultErrorMessage message := defaultErrorMessage
if err.BaseError != nil { if err.BaseError != nil {
message = err.BaseError.Error() message = fmt.Sprintf("%s - %s", defaultErrorMessage, err.BaseError.Error())
} }
if len([]rune(err.ErrorMsg)) > 0 { if len(err.ErrorMsg) > 0 {
message = err.ErrorMsg message = err.ErrorMsg
} }
@ -50,4 +55,22 @@ func NewAPIClientError(responseBody []byte) error {
} }
return &httpClientError{ErrorMsg: message} return &httpClientError{ErrorMsg: message}
} }
func NewServerError(response *http.Response) error {
var data []byte
body, err := buildRawResponse(response)
if err == nil {
data = body
}
err = NewAPIClientError(data)
var serverError *httpClientError
if errors.As(err, &serverError) {
serverError.ResponseBody = response.Body
return serverError
}
return err
}

View File

@ -1,7 +1,11 @@
package v1 package v1
import ( import (
"bytes"
"errors" "errors"
"fmt"
"io"
"net/http"
"net/url" "net/url"
"testing" "testing"
@ -15,7 +19,7 @@ func TestNewCriticalHTTPError(t *testing.T) {
assert.IsType(t, new(httpClientError), httpErr) assert.IsType(t, new(httpClientError), httpErr)
assert.IsType(t, new(url.Error), errors.Unwrap(httpErr)) assert.IsType(t, new(url.Error), errors.Unwrap(httpErr))
assert.IsType(t, new(url.Error), errors.Unwrap(httpErr)) assert.IsType(t, new(url.Error), errors.Unwrap(httpErr))
assert.Equal(t, httpErr.Error(), err.Error()) assert.Equal(t, httpErr.Error(), fmt.Sprintf("%s - %s", defaultErrorMessage, err.Error()))
} }
func TestNewApiClientError(t *testing.T) { func TestNewApiClientError(t *testing.T) {
@ -30,4 +34,21 @@ func TestNewApiClientError(t *testing.T) {
assert.IsType(t, new(httpClientError), httpErr) assert.IsType(t, new(httpClientError), httpErr)
assert.Equal(t, httpErr.Error(), internalServerError) assert.Equal(t, httpErr.Error(), internalServerError)
} }
func TestNewServerError(t *testing.T) {
body := []byte(`{"errors" : ["Something went wrong"]}`)
response := new(http.Response)
response.Body = io.NopCloser(bytes.NewReader(body))
serverErr := NewServerError(response)
assert.IsType(t, new(httpClientError), serverErr)
assert.Equal(t, serverErr.Error(), "Something went wrong")
var err *httpClientError
if errors.As(serverErr, &err) {
assert.NotNil(t, err.ResponseBody)
} else {
t.Fatal("Unexpected type of error")
}
}

17
v1/helpers.go Normal file
View File

@ -0,0 +1,17 @@
package v1
import (
"io"
"net/http"
)
func buildRawResponse(resp *http.Response) ([]byte, error) {
defer resp.Body.Close()
res, err := io.ReadAll(resp.Body)
if err != nil {
return res, err
}
return res, nil
}

View File

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"strings" "strings"
) )
@ -74,30 +73,19 @@ func makeRequest(reqType, url string, buf io.Reader, c *MgClient) ([]byte, int,
return res, 0, NewCriticalHTTPError(err) return res, 0, NewCriticalHTTPError(err)
} }
if resp.StatusCode >= http.StatusInternalServerError {
err = NewServerError(resp)
return res, resp.StatusCode, err
}
res, err = buildRawResponse(resp) res, err = buildRawResponse(resp)
if err != nil { if err != nil {
return res, 0, err return res, 0, err
} }
if resp.StatusCode >= http.StatusInternalServerError {
err = NewAPIClientError(res)
return res, resp.StatusCode, err
}
if c.Debug { if c.Debug {
c.writeLog("MG TRANSPORT API Response: %s", res) c.writeLog("MG TRANSPORT API Response: %s", res)
} }
return res, resp.StatusCode, err return res, resp.StatusCode, err
} }
func buildRawResponse(resp *http.Response) ([]byte, error) {
defer resp.Body.Close()
res, err := ioutil.ReadAll(resp.Body)
if err != nil {
return res, err
}
return res, nil
}

View File

@ -66,23 +66,6 @@ const (
OriginatorChannel OriginatorChannel
) )
// ClientError is error of http-client or network.
type ClientError string
func (err ClientError) Error() string {
return string(err)
}
// APIError is error with 5xx status code.
type APIError struct {
code int
errorMsg string
}
func (err APIError) Error() string {
return fmt.Sprintf("Error message: %s, code: %v", err.errorMsg, err.code)
}
type ErrorType string type ErrorType string
const ( const (