Skip to content
Snippets Groups Projects
Commit 12e9e3c0 authored by Lyuben Penkovski's avatar Lyuben Penkovski
Browse files

Merge branch '1-cache-client' into 'main'

Client of the Cache service

Closes #1

See merge request !6
parents 25fce799 b5584db2
No related branches found
No related tags found
1 merge request!6Client of the Cache service
Pipeline #51052 failed with stage
in 20 seconds
// Package cache implements a client of the Cache service.
package cache
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"net/url"
"code.vereign.com/gaiax/tsa/golib/errors"
)
// Client for the Cache service.
type Client struct {
addr string
httpClient *http.Client
}
func New(addr string, opts ...Option) *Client {
c := &Client{
addr: addr,
httpClient: http.DefaultClient,
}
for _, opt := range opts {
opt(c)
}
return c
}
func (c *Client) Set(ctx context.Context, key, namespace, scope string, value []byte) error {
requestURI := c.addr + "/v1/cache"
cacheURL, err := url.ParseRequestURI(requestURI)
if err != nil {
return errors.New(errors.Internal, "invalid cache url", err)
}
req, err := http.NewRequestWithContext(ctx, "POST", cacheURL.String(), bytes.NewReader(value))
if err != nil {
return err
}
req.Header = http.Header{
"x-cache-key": []string{key},
"x-cache-namespace": []string{namespace},
"x-cache-scope": []string{scope},
}
resp, err := c.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close() // nolint:errcheck
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
msg := fmt.Sprintf("unexpected response: %s", resp.Status)
return errors.New(errors.GetKind(resp.StatusCode), msg)
}
return nil
}
func (c *Client) Get(ctx context.Context, key, namespace, scope string) ([]byte, error) {
requestURI := c.addr + "/v1/cache"
cacheURL, err := url.ParseRequestURI(requestURI)
if err != nil {
return nil, errors.New(errors.Internal, "invalid cache url", err)
}
req, err := http.NewRequestWithContext(ctx, "GET", cacheURL.String(), nil)
req.Header = http.Header{
"x-cache-key": []string{key},
"x-cache-namespace": []string{namespace},
"x-cache-scope": []string{scope},
}
if err != nil {
return nil, err
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close() // nolint:errcheck
if resp.StatusCode != http.StatusOK {
if resp.StatusCode == http.StatusNotFound {
return nil, errors.New(errors.NotFound)
}
msg := fmt.Sprintf("unexpected response: %s", resp.Status)
return nil, errors.New(errors.GetKind(resp.StatusCode), msg)
}
return io.ReadAll(resp.Body)
}
package cache_test
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"code.vereign.com/gaiax/tsa/golib/cache"
"code.vereign.com/gaiax/tsa/golib/errors"
)
func TestClient_InvalidCacheAddress(t *testing.T) {
client := cache.New("dkaslkfasdlkn")
// get
res, err := client.Get(context.Background(), "test", "test", "test")
assert.Nil(t, res)
assert.Error(t, err)
assert.True(t, errors.Is(errors.Internal, err))
assert.Contains(t, err.Error(), "invalid cache url")
// set
err = client.Set(context.Background(), "test", "test", "test", []byte("data"))
assert.Error(t, err)
assert.True(t, errors.Is(errors.Internal, err))
assert.Contains(t, err.Error(), "invalid cache url")
}
func TestClient_Get(t *testing.T) {
tests := []struct {
name string
key string
namespace string
scope string
handler http.HandlerFunc
result []byte
errkind errors.Kind
errtext string
}{
{
name: "cache entry not found",
key: "mykey",
namespace: "mynamespace",
scope: "myscope",
handler: func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "mykey", r.Header.Get("x-cache-key"))
assert.Equal(t, "mynamespace", r.Header.Get("x-cache-namespace"))
assert.Equal(t, "myscope", r.Header.Get("x-cache-scope"))
w.WriteHeader(http.StatusNotFound)
},
errkind: errors.NotFound,
errtext: "not found",
},
{
name: "unexpected error returned from cache",
key: "mykey",
namespace: "mynamespace",
scope: "myscope",
handler: func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "mykey", r.Header.Get("x-cache-key"))
assert.Equal(t, "mynamespace", r.Header.Get("x-cache-namespace"))
assert.Equal(t, "myscope", r.Header.Get("x-cache-scope"))
w.WriteHeader(http.StatusInternalServerError)
},
errkind: errors.Internal,
errtext: "unexpected response: 500 Internal Server Error",
},
{
name: "cache entry is retrieved successfully",
key: "mykey",
namespace: "mynamespace",
scope: "myscope",
handler: func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "mykey", r.Header.Get("x-cache-key"))
assert.Equal(t, "mynamespace", r.Header.Get("x-cache-namespace"))
assert.Equal(t, "myscope", r.Header.Get("x-cache-scope"))
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("data"))
},
result: []byte("data"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
cachesrv := httptest.NewServer(test.handler)
client := cache.New(cachesrv.URL)
result, err := client.Get(context.Background(), test.key, test.namespace, test.scope)
if test.errtext != "" {
assert.Nil(t, result)
assert.Error(t, err)
assert.True(t, errors.Is(test.errkind, err))
assert.Contains(t, err.Error(), test.errtext)
} else {
assert.NoError(t, err)
assert.Equal(t, test.result, result)
}
})
}
}
func TestClient_Set(t *testing.T) {
tests := []struct {
name string
key string
namespace string
scope string
data []byte
handler http.HandlerFunc
result []byte
errkind errors.Kind
errtext string
}{
{
name: "unexpected response returned from cache",
key: "mykey",
namespace: "mynamespace",
scope: "myscope",
handler: func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "mykey", r.Header.Get("x-cache-key"))
assert.Equal(t, "mynamespace", r.Header.Get("x-cache-namespace"))
assert.Equal(t, "myscope", r.Header.Get("x-cache-scope"))
w.WriteHeader(http.StatusInternalServerError)
},
errkind: errors.Internal,
errtext: "unexpected response: 500 Internal Server Error",
},
{
name: "unexpected response returned from cache",
key: "mykey",
namespace: "mynamespace",
scope: "myscope",
handler: func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "mykey", r.Header.Get("x-cache-key"))
assert.Equal(t, "mynamespace", r.Header.Get("x-cache-namespace"))
assert.Equal(t, "myscope", r.Header.Get("x-cache-scope"))
w.WriteHeader(http.StatusRequestTimeout)
},
errkind: errors.Timeout,
errtext: "unexpected response: 408 Request Timeout",
},
{
name: "data is stored in cache successfully",
key: "mykey",
namespace: "mynamespace",
scope: "myscope",
handler: func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "mykey", r.Header.Get("x-cache-key"))
assert.Equal(t, "mynamespace", r.Header.Get("x-cache-namespace"))
assert.Equal(t, "myscope", r.Header.Get("x-cache-scope"))
w.WriteHeader(http.StatusOK)
},
},
{
name: "data is stored in cache successfully",
key: "mykey",
namespace: "mynamespace",
scope: "myscope",
handler: func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "mykey", r.Header.Get("x-cache-key"))
assert.Equal(t, "mynamespace", r.Header.Get("x-cache-namespace"))
assert.Equal(t, "myscope", r.Header.Get("x-cache-scope"))
w.WriteHeader(http.StatusCreated)
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
cachesrv := httptest.NewServer(test.handler)
client := cache.New(cachesrv.URL)
err := client.Set(context.Background(), test.key, test.namespace, test.scope, test.data)
if test.errtext != "" {
assert.Error(t, err)
assert.True(t, errors.Is(test.errkind, err))
assert.Contains(t, err.Error(), test.errtext)
} else {
assert.NoError(t, err)
}
})
}
}
package cache
import (
"net/http"
)
type Option func(*Client)
func WithHTTPClient(client *http.Client) Option {
return func(c *Client) {
c.httpClient = client
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment