Implementation and tests for certmagic.Storage interface

This commit is contained in:
Jake Sanders 2019-10-16 12:58:14 +01:00
parent a6e95d389f
commit 7d2afa34a0
2 changed files with 149 additions and 17 deletions

View File

@ -2,11 +2,16 @@ package certmagic
import ( import (
"os" "os"
"reflect"
"sort"
"testing" "testing"
"time"
"github.com/go-acme/lego/v3/providers/dns/cloudflare" "github.com/go-acme/lego/v3/providers/dns/cloudflare"
"github.com/mholt/certmagic" "github.com/mholt/certmagic"
"github.com/micro/go-micro/api/server/acme" "github.com/micro/go-micro/api/server/acme"
"github.com/micro/go-micro/config/options"
cloudflarestorage "github.com/micro/go-micro/store/cloudflare"
"github.com/micro/go-micro/sync/lock/memory" "github.com/micro/go-micro/sync/lock/memory"
) )
@ -43,13 +48,140 @@ func TestCertMagic(t *testing.T) {
} }
func TestStorageImplementation(t *testing.T) { func TestStorageImplementation(t *testing.T) {
var s certmagic.Storage apiToken, accountID := os.Getenv("CF_API_TOKEN"), os.Getenv("CF_ACCOUNT_ID")
s = &storage{ kvID := os.Getenv("KV_NAMESPACE_ID")
lock: memory.NewLock(), if len(apiToken) == 0 || len(accountID) == 0 || len(kvID) == 0 {
t.Skip("No Cloudflare API keys available, skipping test")
} }
var s certmagic.Storage
st, err := cloudflarestorage.New(
options.WithValue("CF_API_TOKEN", apiToken),
options.WithValue("CF_ACCOUNT_ID", accountID),
options.WithValue("KV_NAMESPACE_ID", kvID),
)
if err != nil {
t.Fatalf("Couldn't initialise cloudflare storage: %s\n", err.Error())
}
s = &storage{
lock: memory.NewLock(),
store: st,
}
// Test Lock
if err := s.Lock("test"); err != nil { if err := s.Lock("test"); err != nil {
t.Error(err) t.Error(err)
} }
s.Unlock("test")
// Test Unlock
if err := s.Unlock("test"); err != nil {
t.Error(err)
}
// Test data
testdata := []struct {
key string
value []byte
}{
{key: "/foo/a", value: []byte("lorem")},
{key: "/foo/b", value: []byte("ipsum")},
{key: "/foo/c", value: []byte("dolor")},
{key: "/foo/d", value: []byte("sit")},
{key: "/bar/a", value: []byte("amet")},
{key: "/bar/b", value: []byte("consectetur")},
{key: "/bar/c", value: []byte("adipiscing")},
{key: "/bar/d", value: []byte("elit")},
{key: "/foo/bar/a", value: []byte("sed")},
{key: "/foo/bar/b", value: []byte("do")},
{key: "/foo/bar/c", value: []byte("eiusmod")},
{key: "/foo/bar/d", value: []byte("tempor")},
{key: "/foo/bar/baz/a", value: []byte("incididunt")},
{key: "/foo/bar/baz/b", value: []byte("ut")},
{key: "/foo/bar/baz/c", value: []byte("labore")},
{key: "/foo/bar/baz/d", value: []byte("et")},
// a duplicate just in case there's any edge cases
{key: "/foo/a", value: []byte("lorem")},
}
// Test Store
for _, d := range testdata {
if err := s.Store(d.key, d.value); err != nil {
t.Error(err.Error())
}
}
// Test Load
for _, d := range testdata {
if value, err := s.Load(d.key); err != nil {
t.Error(err.Error())
} else {
if !reflect.DeepEqual(value, d.value) {
t.Errorf("Load %s: expected %v, got %v", d.key, d.value, value)
}
}
}
// Test Exists
for _, d := range testdata {
if !s.Exists(d.key) {
t.Errorf("%s should exist, but doesn't\n", d.key)
}
}
// Test List
if list, err := s.List("/", true); err != nil {
t.Error(err.Error())
} else {
var expected []string
for i, d := range testdata {
if i != len(testdata)-1 {
// Don't store the intentionally duplicated key
expected = append(expected, d.key)
}
}
sort.Strings(expected)
sort.Strings(list)
if !reflect.DeepEqual(expected, list) {
t.Errorf("List: Expected %v, got %v\n", expected, list)
}
}
if list, err := s.List("/foo", false); err != nil {
t.Error(err.Error())
} else {
sort.Strings(list)
expected := []string{"/foo/a", "/foo/b", "/foo/bar", "/foo/c", "/foo/d"}
if !reflect.DeepEqual(expected, list) {
t.Errorf("List: expected %s, got %s\n", expected, list)
}
}
// Test Stat
for _, d := range testdata {
info, err := s.Stat(d.key)
if err != nil {
t.Error(err.Error())
} else {
if info.Key != d.key {
t.Errorf("Stat().Key: expected %s, got %s\n", d.key, info.Key)
}
if info.Size != int64(len(d.value)) {
t.Errorf("Stat().Size: expected %d, got %d\n", len(d.value), info.Size)
}
if time.Since(info.Modified) > time.Minute {
t.Errorf("Stat().Modified: expected time since last modified to be < 1 minute, got %v\n", time.Since(info.Modified))
}
}
}
// Test Delete
for _, d := range testdata {
if err := s.Delete(d.key); err != nil {
t.Error(err.Error())
}
}
// New interface doesn't return an error, so call it in case any log.Fatal
// happens
New(acme.Cache(s)) New(acme.Cache(s))
} }

View File

@ -13,12 +13,12 @@ import (
"github.com/micro/go-micro/sync/lock" "github.com/micro/go-micro/sync/lock"
) )
// file represents a "file" that will be stored in store.Store - the contents and last modified time // File represents a "File" that will be stored in store.Store - the contents and last modified time
type file struct { type File struct {
// last modified time // last modified time
lastModified time.Time LastModified time.Time
// contents // Contents
contents []byte Contents []byte
} }
// storage is an implementation of certmagic.Storage using micro's sync.Map and store.Store interfaces. // storage is an implementation of certmagic.Storage using micro's sync.Map and store.Store interfaces.
@ -38,9 +38,9 @@ func (s *storage) Unlock(key string) error {
} }
func (s *storage) Store(key string, value []byte) error { func (s *storage) Store(key string, value []byte) error {
f := file{ f := File{
lastModified: time.Now(), LastModified: time.Now(),
contents: value, Contents: value,
} }
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
e := gob.NewEncoder(buf) e := gob.NewEncoder(buf)
@ -64,12 +64,12 @@ func (s *storage) Load(key string) ([]byte, error) {
} }
b := bytes.NewBuffer(records[0].Value) b := bytes.NewBuffer(records[0].Value)
d := gob.NewDecoder(b) d := gob.NewDecoder(b)
var f file var f File
err = d.Decode(&f) err = d.Decode(&f)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return f.contents, nil return f.Contents, nil
} }
func (s *storage) Delete(key string) error { func (s *storage) Delete(key string) error {
@ -120,15 +120,15 @@ func (s *storage) Stat(key string) (certmagic.KeyInfo, error) {
} }
b := bytes.NewBuffer(records[0].Value) b := bytes.NewBuffer(records[0].Value)
d := gob.NewDecoder(b) d := gob.NewDecoder(b)
var f file var f File
err = d.Decode(&f) err = d.Decode(&f)
if err != nil { if err != nil {
return certmagic.KeyInfo{}, err return certmagic.KeyInfo{}, err
} }
return certmagic.KeyInfo{ return certmagic.KeyInfo{
Key: key, Key: key,
Modified: f.lastModified, Modified: f.LastModified,
Size: int64(len(f.contents)), Size: int64(len(f.Contents)),
IsTerminal: false, IsTerminal: false,
}, nil }, nil
} }