-
-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BACK-2500
- Loading branch information
Showing
5 changed files
with
195 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package alerts | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
|
||
"github.com/tidepool-org/platform/auth" | ||
"github.com/tidepool-org/platform/request" | ||
) | ||
|
||
// Client for managing alerts configs. | ||
type Client struct { | ||
client PlatformClient | ||
token TokenProvider | ||
} | ||
|
||
// NewClient builds a client for interacting with alerts API endpoints. | ||
func NewClient(client PlatformClient, token TokenProvider) *Client { | ||
return &Client{ | ||
client: client, | ||
token: token, | ||
} | ||
} | ||
|
||
// platform.Client is one implementation | ||
type PlatformClient interface { | ||
ConstructURL(paths ...string) string | ||
RequestData(ctx context.Context, method string, url string, mutators []request.RequestMutator, | ||
requestBody interface{}, responseBody interface{}, inspectors ...request.ResponseInspector) error | ||
} | ||
|
||
// client.External is one implementation | ||
type TokenProvider interface { | ||
// ServerSessionToken provides a server-to-server API authentication token. | ||
ServerSessionToken() (string, error) | ||
} | ||
|
||
// requestWithAuth injects an auth token before calling platform.Client.RequestData. | ||
// | ||
// At time of writing, this is the only way to inject credentials into | ||
// platform.Client. It might be nice to be able to use a mutator, but the auth | ||
// is specifically handled by the platform.Client via the context field, and | ||
// if left blank, platform.Client errors. | ||
func (c *Client) requestWithAuth(ctx context.Context, method, url string, body any) error { | ||
authCtx, err := c.ctxWithAuth(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
return c.client.RequestData(authCtx, method, url, nil, body, nil) | ||
} | ||
|
||
// Upsert updates cfg if it exists or creates it if it doesn't. | ||
func (c *Client) Upsert(ctx context.Context, cfg *Config) error { | ||
url := c.client.ConstructURL("v1", "alerts", cfg.UserID, cfg.FollowedUserID) | ||
return c.requestWithAuth(ctx, http.MethodPost, url, cfg) | ||
} | ||
|
||
// Delete the alerts config. | ||
func (c *Client) Delete(ctx context.Context, cfg *Config) error { | ||
url := c.client.ConstructURL("v1", "alerts", cfg.UserID, cfg.FollowedUserID) | ||
return c.requestWithAuth(ctx, http.MethodDelete, url, nil) | ||
} | ||
|
||
// ctxWithAuth injects a server session token into the context. | ||
func (c *Client) ctxWithAuth(ctx context.Context) (context.Context, error) { | ||
token, err := c.token.ServerSessionToken() | ||
if err != nil { | ||
return nil, err | ||
} | ||
return auth.NewContextWithServerSessionToken(ctx, token), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package alerts | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
"net/http/httptest" | ||
|
||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/gomega" | ||
|
||
"github.com/tidepool-org/platform/auth" | ||
"github.com/tidepool-org/platform/client" | ||
"github.com/tidepool-org/platform/log" | ||
"github.com/tidepool-org/platform/log/null" | ||
"github.com/tidepool-org/platform/platform" | ||
) | ||
|
||
const testToken = "auth-me" | ||
|
||
var _ = Describe("Client", func() { | ||
t := GinkgoT() | ||
test404Server := testServer(t, func(w http.ResponseWriter, r *http.Request) { | ||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) | ||
}) | ||
test200Server := testServer(t, func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusOK) | ||
}) | ||
testAuthServer := func(token *string) *httptest.Server { | ||
return testServer(t, func(w http.ResponseWriter, r *http.Request) { | ||
*token = r.Header.Get(auth.TidepoolSessionTokenHeaderKey) | ||
w.WriteHeader(http.StatusOK) | ||
}) | ||
} | ||
|
||
Context("Delete", func() { | ||
It("returns an error on non-200 responses", func() { | ||
client, ctx := newAlertsClientTest(test404Server) | ||
err := client.Delete(ctx, &Config{}) | ||
Expect(err).Should(HaveOccurred()) | ||
Expect(err).To(MatchError(ContainSubstring("resource not found"))) | ||
}) | ||
|
||
It("returns nil on success", func() { | ||
client, ctx := newAlertsClientTest(test200Server) | ||
err := client.Delete(ctx, &Config{}) | ||
Expect(err).ShouldNot(HaveOccurred()) | ||
}) | ||
|
||
It("injects an auth token", func() { | ||
token := "" | ||
client, ctx := newAlertsClientTest(testAuthServer(&token)) | ||
_ = client.Delete(ctx, &Config{}) | ||
Expect(token).To(Equal(testToken)) | ||
}) | ||
}) | ||
|
||
Context("Upsert", func() { | ||
It("returns an error on non-200 responses", func() { | ||
client, ctx := newAlertsClientTest(test404Server) | ||
err := client.Upsert(ctx, &Config{}) | ||
Expect(err).Should(HaveOccurred()) | ||
Expect(err).To(MatchError(ContainSubstring("resource not found"))) | ||
}) | ||
|
||
It("returns nil on success", func() { | ||
client, ctx := newAlertsClientTest(test200Server) | ||
err := client.Upsert(ctx, &Config{}) | ||
Expect(err).ShouldNot(HaveOccurred()) | ||
}) | ||
|
||
It("injects an auth token", func() { | ||
token := "" | ||
client, ctx := newAlertsClientTest(testAuthServer(&token)) | ||
_ = client.Upsert(ctx, &Config{}) | ||
Expect(token).To(Equal(testToken)) | ||
}) | ||
}) | ||
}) | ||
|
||
func buildTestClient(s *httptest.Server) *Client { | ||
pCfg := &platform.Config{ | ||
Config: &client.Config{ | ||
Address: s.URL, | ||
UserAgent: "foo", | ||
}, | ||
} | ||
token := mockTokenProvider(testToken) | ||
pc, err := platform.NewClient(pCfg, platform.AuthorizeAsService) | ||
Expect(err).ToNot(HaveOccurred()) | ||
client := NewClient(pc, token) | ||
return client | ||
} | ||
|
||
func newAlertsClientTest(server *httptest.Server) (*Client, context.Context) { | ||
return buildTestClient(server), contextWithNullLogger() | ||
} | ||
|
||
func contextWithNullLogger() context.Context { | ||
return log.NewContextWithLogger(context.Background(), null.NewLogger()) | ||
} | ||
|
||
type mockTokenProvider string | ||
|
||
func (p mockTokenProvider) ServerSessionToken() (string, error) { | ||
return string(p), nil | ||
} | ||
|
||
func testServer(t GinkgoTInterface, handler http.HandlerFunc) *httptest.Server { | ||
s := httptest.NewServer(http.HandlerFunc(handler)) | ||
t.Cleanup(s.Close) | ||
return s | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters