add image storage to memory_store

This commit is contained in:
Dmitry Verkhoturov
2020-04-04 14:06:39 -05:00
committed by Umputun
parent ce92f63215
commit 0355ba4ff7
9 changed files with 428 additions and 5 deletions
@@ -0,0 +1,69 @@
/*
* Copyright 2020 Umputun. All rights reserved.
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file.
*/
package server
import (
"context"
"encoding/base64"
"encoding/json"
"time"
"github.com/go-pkgz/jrpc"
)
func (s *RPC) imgSaveHndl(id uint64, params json.RawMessage) (rr jrpc.Response) {
var req [2]string
if err := json.Unmarshal(params, &req); err != nil {
return jrpc.Response{Error: err.Error()}
}
img, err := base64.StdEncoding.DecodeString(req[1])
if err != nil {
return jrpc.Response{Error: err.Error()}
}
value, err := s.img.Save(req[0], img)
return jrpc.EncodeResponse(id, value, err)
}
func (s *RPC) imgSaveWithIDHndl(id uint64, params json.RawMessage) (rr jrpc.Response) {
var req [2]string
if err := json.Unmarshal(params, &req); err != nil {
return jrpc.Response{Error: err.Error()}
}
img, err := base64.StdEncoding.DecodeString(req[1])
if err != nil {
return jrpc.Response{Error: err.Error()}
}
value, err := s.img.SaveWithID(req[0], img)
return jrpc.EncodeResponse(id, value, err)
}
func (s *RPC) imgLoadHndl(id uint64, params json.RawMessage) (rr jrpc.Response) {
var fileID string
if err := json.Unmarshal(params, &fileID); err != nil {
return jrpc.Response{Error: err.Error()}
}
value, err := s.img.Load(fileID)
return jrpc.EncodeResponse(id, value, err)
}
func (s *RPC) imgCommitHndl(id uint64, params json.RawMessage) (rr jrpc.Response) {
var fileID string
if err := json.Unmarshal(params, &fileID); err != nil {
return jrpc.Response{Error: err.Error()}
}
err := s.img.Commit(fileID)
return jrpc.EncodeResponse(id, nil, err)
}
func (s *RPC) imgCleanupHndl(id uint64, params json.RawMessage) (rr jrpc.Response) {
var ttl time.Duration
if err := json.Unmarshal(params, &ttl); err != nil {
return jrpc.Response{Error: err.Error()}
}
err := s.img.Cleanup(context.TODO(), ttl)
return jrpc.EncodeResponse(id, nil, err)
}
@@ -0,0 +1,138 @@
/*
* Copyright 2020 Umputun. All rights reserved.
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file.
*/
package server
import (
"context"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"testing"
"time"
"github.com/go-pkgz/jrpc"
"github.com/stretchr/testify/assert"
"github.com/umputun/remark/backend/app/store/image"
)
// gopher png for test, from https://golang.org/src/image/png/example_test.go
const gopher = "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"
func gopherPNG() io.Reader { return base64.NewDecoder(base64.StdEncoding, strings.NewReader(gopher)) }
func gopherPNGBytes() []byte {
img, _ := ioutil.ReadAll(gopherPNG())
return img
}
func TestRPC_imgSaveHndl(t *testing.T) {
_, port, teardown := prepTestStore(t)
defer teardown()
api := fmt.Sprintf("http://localhost:%d/test", port)
ri := image.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}}
id, err := ri.Save("admin", gopherPNGBytes())
assert.NoError(t, err)
assert.Contains(t, id, "admin/", "id contains username")
err = ri.Commit(id)
assert.NoError(t, err)
}
func TestRPC_imgSaveWithIDHndl(t *testing.T) {
_, port, teardown := prepTestStore(t)
defer teardown()
api := fmt.Sprintf("http://localhost:%d/test", port)
ri := image.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}}
id, err := ri.SaveWithID("test_id", gopherPNGBytes())
assert.NoError(t, err)
assert.Equal(t, id, "test_id")
}
func TestRPC_imgLoadHndl(t *testing.T) {
_, port, teardown := prepTestStore(t)
defer teardown()
api := fmt.Sprintf("http://localhost:%d/test", port)
ri := image.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}}
// save
id, err := ri.Save("admin", gopherPNGBytes())
assert.NoError(t, err)
// load
img, err := ri.Load(id)
assert.NoError(t, err)
assert.Equal(t, 1462, len(img))
assert.Equal(t, gopherPNGBytes(), img)
// commit
err = ri.Commit(id)
assert.NoError(t, err)
// load after commit
img, err = ri.Load(id)
assert.NoError(t, err)
assert.Equal(t, 1462, len(img))
assert.Equal(t, gopherPNGBytes(), img)
}
func TestRPC_imgCommitHndlFail(t *testing.T) {
_, port, teardown := prepTestStore(t)
defer teardown()
api := fmt.Sprintf("http://localhost:%d/test", port)
ri := image.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}}
err := ri.Commit("test_id")
assert.EqualError(t, err, "failed to commit test_id, not found in staging")
}
func TestRPC_imgCleanupHndl(t *testing.T) {
_, port, teardown := prepTestStore(t)
defer teardown()
api := fmt.Sprintf("http://localhost:%d/test", port)
ri := image.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}}
// save
id, err := ri.Save("admin", gopherPNGBytes())
assert.NoError(t, err)
// load
_, err = ri.Load(id)
assert.NoError(t, err)
// cleanup
err = ri.Cleanup(context.TODO(), time.Nanosecond)
assert.NoError(t, err)
// load after cleanup should fail
_, err = ri.Load(id)
assert.Error(t, err)
assert.Contains(t, err.Error(), "image admin/")
assert.Contains(t, err.Error(), "not found")
}
+13 -2
View File
@@ -8,6 +8,7 @@ package server
import (
"github.com/go-pkgz/jrpc"
"github.com/umputun/remark/backend/app/store/image"
"github.com/umputun/remark/backend/app/store/admin"
"github.com/umputun/remark/backend/app/store/engine"
@@ -19,11 +20,12 @@ type RPC struct {
*jrpc.Server
eng engine.Interface
adm admin.Store
img image.Store
}
// NewRPC makes RPC instance and register handlers
func NewRPC(e engine.Interface, a admin.Store, r *jrpc.Server) *RPC {
res := &RPC{eng: e, adm: a, Server: r}
func NewRPC(e engine.Interface, a admin.Store, i image.Store, r *jrpc.Server) *RPC {
res := &RPC{eng: e, adm: a, img: i, Server: r}
res.addHandlers()
return res
}
@@ -52,4 +54,13 @@ func (s *RPC) addHandlers() {
"enabled": s.admEnabledHndl,
"event": s.admEventHndl,
})
// image store handlers
s.Group("image", jrpc.HandlersGroup{
"save": s.imgSaveHndl,
"save_with_id": s.imgSaveWithIDHndl,
"load": s.imgLoadHndl,
"commit": s.imgCommitHndl,
"cleanup": s.imgCleanupHndl,
})
}
@@ -47,7 +47,8 @@ func waitForHTTPServerStart(port int) {
func prepTestStore(t *testing.T) (s *RPC, port int, teardown func()) {
mg := accessor.NewMemData()
adm := accessor.NewMemAdminStore("secret")
s = NewRPC(mg, adm, &jrpc.Server{API: "/test", Logger: jrpc.NoOpLogger})
img := accessor.NewMemImageStore()
s = NewRPC(mg, adm, img, &jrpc.Server{API: "/test", Logger: jrpc.NoOpLogger})
admRec := accessor.AdminRec{
SiteID: "test-site",