Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 61 additions & 5 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"io"
"log"
"net/http"
"net/url"
"strings"
)

const (
Expand All @@ -27,10 +29,24 @@ var (
ErrBadRequest = errors.New("error: bad request")
)

// Error API Response. Contains the error message as well as the type of error
type Error struct {
Comment thread
This conversation was marked as resolved.
Message string `json:"message"`
Type string `json:"type"`
}

//URLBuilder is the interface for building URLs
//go:generate mockery --name URLBuilder
type URLBuilder interface {
SearchDocumentURL(vaultID string) string
GetUserURL(userId []string) string

@Sibley199 Sibley199 Oct 27, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure how I feel about this pattern. The client needs to know so much about other packages. I wonder if it would be easier if we just mocked the Do function in our tests then we can move the url stuff to the appropriate packages. @dooven Thoughts?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this is fine. The client only needs to know about the URLBuilder interface, not its implementation. The nice thing about this URL builders is we can mock the response using the HTTP test and actually use the http client on tests

func Test_trueVaultClient_SearchDocument_ReturnsSearchResult(t *testing.T) {
expectedResult := SearchDocumentResult{Result: "Success"}
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(expectedResult)
}))
urlBuilder := new(_clientMock.URLBuilder)
urlBuilder.On("SearchDocumentURL", mock.Anything).Once().Return(ts.URL)
service := New(gotruevault.New(http.DefaultClient, urlBuilder, ""))
result, err := service.SearchDocument(context.TODO(), "testing", gotruevault.SearchOption{})
assert.Nil(t, err)
assert.Equal(t, result, expectedResult)

I guess the reason why it feels like the client is doing so much is that this interface is in client.go. We can move this to a separate file if that helps

CreateUserURL() string
ListUserURL(queryParams url.Values) string
UpdateUserURL(userId string) string
UpdateUserPasswordURL(userId string) string
DeleteUserURL(userId string) string
CreateAccessTokenURL(userId string) string
CreateApiKeyURL(userId string) string
}

//DefaultURLBuilder implements URLBuilder interface
Expand All @@ -41,6 +57,50 @@ func (t *DefaultURLBuilder) SearchDocumentURL(vaultID string) string {
return fmt.Sprintf("https://api.truevault.com/v1/vaults/%s/search", vaultID)
}

// GetUserURL returns the TrueVault `Get User` route for the specified user id(s)
func (t *DefaultURLBuilder) GetUserURL(userId []string) string {
return fmt.Sprintf("https://api.truevault.com/v2/users/"+strings.Join(userId, ","))
}

// CreateUserURL returns the TrueVault `Create User` route
func (t *DefaultURLBuilder) CreateUserURL() string {
return "https://api.truevault.com/v1/users"
}

// UpdateUserURL returns the TrueVault `Update User` route
func (t *DefaultURLBuilder) UpdateUserURL(userId string) string {
return "https://api.truevault.com/v1/users/" + userId
}

// UpdateUserPasswordURL returns the TrueVault `Update User Password` route
func (t *DefaultURLBuilder) UpdateUserPasswordURL(userId string) string {
return "https://api.truevault.com/v1/users/" + userId
}

// DeleteUserURL returns the TrueVault `Delete User` route
func (t *DefaultURLBuilder) DeleteUserURL(userId string) string {
return "https://api.truevault.com/v1/users/" + userId
}

// CreateAccessTokenURL returns the TrueVault `Create Access Token` route
func (t *DefaultURLBuilder) CreateAccessTokenURL(userId string) string {
return "https://api.truevault.com/v1/users/" + userId
}

// CreateApiKeyURL returns the TrueVault `Create API Key` route
func (t *DefaultURLBuilder) CreateApiKeyURL(userId string) string {
return "https://api.truevault.com/v1/users/" + userId + "/api_key"
}

// ListUserURL returns the TrueVault `List User` route
func (t *DefaultURLBuilder) ListUserURL(queryParams url.Values) string {
params := "?"
if queryParams != nil {
params += queryParams.Encode()
}
return fmt.Sprintf("https://api.truevault.com/v2/users/?%s", params)
}

//Client contains the base http requirements to make requests to TrueVault
type Client struct {
URLBuilder URLBuilder
Expand All @@ -53,7 +113,7 @@ func New(h *http.Client, ub URLBuilder, accessTokenOrKey string) Client {
return Client{
httpClient: h,
URLBuilder: ub,
authorization: buildAuthorizationValue(accessTokenOrKey),
authorization: "Basic " + base64.StdEncoding.EncodeToString([]byte(accessTokenOrKey+":")),
}
}

Expand All @@ -67,10 +127,6 @@ func (c *Client) WithNewAccessTokenOrKey(accessTokenOrKey string) Client {
return New(c.httpClient, c.URLBuilder, accessTokenOrKey)
}

func buildAuthorizationValue(key string) string {
return "Basic " + base64.StdEncoding.EncodeToString([]byte(key+":"))
}

// NewRequest builds an http.Request that contains the Authorization and Content-Type header
func (c *Client) NewRequest(ctx context.Context, method, path, contentType string, body io.Reader) (*http.Request, error) {
req, err := http.NewRequestWithContext(ctx, method, path, body)
Expand Down
8 changes: 2 additions & 6 deletions document/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,17 @@ func New(client gotruevault.Client) Document {

// SearchDocument https://docs.truevault.com/documentsearch#search-documents
func (r *TrueVaultDocument) SearchDocument(ctx context.Context, vaultID string, filter gotruevault.SearchOption) (SearchDocumentResult, error) {
var result SearchDocumentResult
buf := new(bytes.Buffer)
if err := json.NewEncoder(buf).Encode(filter); err != nil {
return SearchDocumentResult{}, err
}

path := r.URLBuilder.SearchDocumentURL(vaultID)

req, err := r.NewRequest(ctx, http.MethodPost, path, gotruevault.ContentTypeApplicationJSON, buf)

if err != nil {
return SearchDocumentResult{}, err
}

err = r.Do(req, &result)

return result, err
var result SearchDocumentResult
Comment thread
This conversation was marked as resolved.
return result, r.Do(req, &result)
}
2 changes: 1 addition & 1 deletion document/mocks/Document.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

120 changes: 118 additions & 2 deletions mocks/URLBuilder.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading