Implementation and tests for certmagic.Storage interface
This commit is contained in:
		| @@ -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)) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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 | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user