implement user details storage (#469)
* implement (strings) user details storage
* add rpc user details implementation
* return error from getUserDetail, rewrite tests to table tests
* make UserDetails store UserDetailEntry instead of strings
* update comment about user_details
* fix confusing return
* add user details support for memory store
* add engine.UserDetailEntry to service.UserMetaData
* add ListDetails support to memory storage
* add user details support to native migrator, ListDetails func to storage
* go mod tidy for memory storage
* increase memory storage test coverage, fix tests naming
* add ListDetails tests to memory storage
* add engine.ListDetails and service.[Set]Metas tests
* change Fprintf to Fprint (triggered by explicitly ignoring error)
* remove Delete from engine.UserDetail, implement list via same method
* adjust service.Metas to new engine.UserDetails signature
* introduce engine.UserDetail("all") consonant
* fix Meta user detail retrieval
* extend store implementations Delete method with UserDetail deletion
* make UserDetail test answer order-independent
* fix flaky test check in TestMemData_FlagListBlocked
* delete user details alongside with comments on deleteme request
* add tests to UserDetail store.Delete implementations
* clarify engine module user details consonants names
* update comments to reflect current state of code
* check for value absence instead of it's length
* revert unneeded code change
* add extensive commentary on UserDetail return type
* remove unused check condition
* clarify UserDetail tests to be truly stateless
* add clarifying comment for pre-table test
This commit is contained in:
committed by
Umputun
parent
773da16649
commit
ddd466ec41
@@ -28,16 +28,17 @@ func NewRPC(e engine.Interface, a admin.Store, r *jrpc.Server) *RPC {
|
||||
func (s *RPC) addHandlers() {
|
||||
// data store handlers
|
||||
s.Group("store", jrpc.HandlersGroup{
|
||||
"create": s.createHndl,
|
||||
"find": s.findHndl,
|
||||
"get": s.getHndl,
|
||||
"update": s.updateHndl,
|
||||
"count": s.countHndl,
|
||||
"info": s.infoHndl,
|
||||
"flag": s.flagHndl,
|
||||
"list_flags": s.listFlagsHndl,
|
||||
"delete": s.deleteHndl,
|
||||
"close": s.closeHndl,
|
||||
"create": s.createHndl,
|
||||
"find": s.findHndl,
|
||||
"get": s.getHndl,
|
||||
"update": s.updateHndl,
|
||||
"count": s.countHndl,
|
||||
"info": s.infoHndl,
|
||||
"flag": s.flagHndl,
|
||||
"list_flags": s.listFlagsHndl,
|
||||
"user_detail": s.userDetailHndl,
|
||||
"delete": s.deleteHndl,
|
||||
"close": s.closeHndl,
|
||||
})
|
||||
|
||||
// admin store handlers
|
||||
@@ -129,7 +130,19 @@ func (s *RPC) listFlagsHndl(id uint64, params json.RawMessage) (rr jrpc.Response
|
||||
return jrpc.EncodeResponse(id, flags, err)
|
||||
}
|
||||
|
||||
// deleteHndl delete post(s), user, comment, or everything
|
||||
// userDetailHndl sets or gets single detail value, or gets all details for requested site.
|
||||
// userDetailHndl returns list even for single entry request is a compromise in order to have both single detail getting and setting
|
||||
// and all site's details listing under the same function (and not to extend engine interface by two separate functions).
|
||||
func (s *RPC) userDetailHndl(id uint64, params json.RawMessage) (rr jrpc.Response) {
|
||||
req := engine.UserDetailRequest{}
|
||||
if err := json.Unmarshal(params, &req); err != nil {
|
||||
return jrpc.Response{Error: err.Error()}
|
||||
}
|
||||
value, err := s.eng.UserDetail(req)
|
||||
return jrpc.EncodeResponse(id, value, err)
|
||||
}
|
||||
|
||||
// deleteHndl delete post(s), user, comment, user details, or everything
|
||||
func (s *RPC) deleteHndl(id uint64, params json.RawMessage) (rr jrpc.Response) {
|
||||
req := engine.DeleteRequest{}
|
||||
if err := json.Unmarshal(params, &req); err != nil {
|
||||
|
||||
@@ -223,6 +223,59 @@ func TestRPC_listFlagsHndl(t *testing.T) {
|
||||
assert.Equal(t, []interface{}{"u1"}, flags)
|
||||
}
|
||||
|
||||
func TestRPC_userDetailHndl(t *testing.T) {
|
||||
_, port, teardown := prepTestStore(t)
|
||||
defer teardown()
|
||||
api := fmt.Sprintf("http://localhost:%d/test", port)
|
||||
|
||||
re := engine.RPC{Client: jrpc.Client{API: api, Client: http.Client{Timeout: 1 * time.Second}}}
|
||||
|
||||
// add to entries to DB before we start
|
||||
result, err := re.UserDetail(engine.UserDetailRequest{Locator: store.Locator{SiteID: "test-site"}, UserID: "u1", Detail: engine.UserEmail, Update: "test@example.com"})
|
||||
assert.NoError(t, err, "No error inserting entry expected")
|
||||
assert.ElementsMatch(t, []engine.UserDetailEntry{{UserID: "u1", Email: "test@example.com"}}, result)
|
||||
result, err = re.UserDetail(engine.UserDetailRequest{Locator: store.Locator{SiteID: "test-site"}, UserID: "u2", Detail: engine.UserEmail, Update: "other@example.com"})
|
||||
assert.NoError(t, err, "No error inserting entry expected")
|
||||
assert.ElementsMatch(t, []engine.UserDetailEntry{{UserID: "u2", Email: "other@example.com"}}, result)
|
||||
|
||||
// try to change existing entry with wrong SiteID
|
||||
result, err = re.UserDetail(engine.UserDetailRequest{Locator: store.Locator{SiteID: "bad"}, UserID: "u2", Detail: engine.UserEmail, Update: "not_relevant"})
|
||||
assert.NoError(t, err, "Updating existing entry with wrong SiteID doesn't produce error")
|
||||
assert.ElementsMatch(t, []engine.UserDetailEntry{}, result, "Updating existing entry with wrong SiteID doesn't change anything")
|
||||
|
||||
// stateless tests without changing the state we set up before
|
||||
var testData = []struct {
|
||||
req engine.UserDetailRequest
|
||||
error string
|
||||
expected []engine.UserDetailEntry
|
||||
}{
|
||||
{req: engine.UserDetailRequest{Locator: store.Locator{SiteID: "test-site"}, UserID: "u1", Detail: engine.UserEmail},
|
||||
expected: []engine.UserDetailEntry{{UserID: "u1", Email: "test@example.com"}}},
|
||||
{req: engine.UserDetailRequest{Locator: store.Locator{SiteID: "bad"}, UserID: "u1", Detail: engine.UserEmail},
|
||||
expected: []engine.UserDetailEntry{}},
|
||||
{req: engine.UserDetailRequest{Locator: store.Locator{SiteID: "test-site"}, UserID: "u1xyz", Detail: engine.UserEmail},
|
||||
expected: []engine.UserDetailEntry{}},
|
||||
{req: engine.UserDetailRequest{Detail: engine.UserEmail, Update: "new_value"},
|
||||
error: `userid cannot be empty in request for single detail`},
|
||||
{req: engine.UserDetailRequest{Detail: engine.UserDetail("bad")},
|
||||
error: `unsupported detail "bad"`},
|
||||
{req: engine.UserDetailRequest{Update: "not_relevant", Detail: engine.AllUserDetails},
|
||||
error: `unsupported request with userdetail all`},
|
||||
{req: engine.UserDetailRequest{Locator: store.Locator{SiteID: "test-site"}, Detail: engine.AllUserDetails},
|
||||
expected: []engine.UserDetailEntry{{UserID: "u1", Email: "test@example.com"}, {UserID: "u2", Email: "other@example.com"}}},
|
||||
}
|
||||
|
||||
for i, x := range testData {
|
||||
result, err := re.UserDetail(x.req)
|
||||
if x.error != "" {
|
||||
assert.EqualError(t, err, x.error, "Error should match expected for case %d", i)
|
||||
} else {
|
||||
assert.NoError(t, err, "Error is not expected expected for case %d", i)
|
||||
}
|
||||
assert.ElementsMatch(t, x.expected, result, "Result should match expected for case %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRPC_deleteHndl(t *testing.T) {
|
||||
_, port, teardown := prepTestStore(t)
|
||||
defer teardown()
|
||||
|
||||
Reference in New Issue
Block a user