3
0

Add record procedure exec capabilities

This will allow us to perform bulk operations on record & record-values
This commit is contained in:
Denis Arh 2019-09-12 18:34:09 +02:00
parent 05dfa30500
commit dc99bc2cb8
14 changed files with 565 additions and 53 deletions

View File

@ -765,6 +765,30 @@
]
}
},
{
"name": "exec",
"path": "/exec/{procedure}",
"method": "POST",
"title": "Executes server-side procedure over one or more module records",
"parameters": {
"path": [
{
"type": "string",
"name": "procedure",
"required": true,
"title": "Name of procedure to execute"
}
],
"post": [
{
"type": "[]ProcedureArg",
"name": "args",
"required": false,
"title": "Procedure arguments"
}
]
}
},
{
"name": "create",
"method": "POST",

View File

@ -189,6 +189,30 @@
]
}
},
{
"Name": "exec",
"Method": "POST",
"Title": "Executes server-side procedure over one or more module records",
"Path": "/exec/{procedure}",
"Parameters": {
"path": [
{
"name": "procedure",
"required": true,
"title": "Name of procedure to execute",
"type": "string"
}
],
"post": [
{
"name": "args",
"required": false,
"title": "Procedure arguments",
"type": "[]ProcedureArg"
}
]
}
},
{
"Name": "create",
"Method": "POST",

View File

@ -32,6 +32,7 @@ type (
LoadValues(fieldNames []string, IDs []uint64) (rvs types.RecordValueSet, err error)
DeleteValues(record *types.Record) error
UpdateValues(recordID uint64, rvs types.RecordValueSet) (err error)
PartialUpdateValues(rvs ...*types.RecordValue) (err error)
}
record struct {
@ -118,7 +119,6 @@ func (r record) Report(module *types.Module, metrics, dimensions, filter string)
func (r record) Find(module *types.Module, filter types.RecordFilter) (set types.RecordSet, f types.RecordFilter, err error) {
var query squirrel.SelectBuilder
f = filter
f.PageFilter.NormalizePerPageWithDefaults()
query, err = r.buildQuery(module, filter)
if err != nil {
@ -314,6 +314,15 @@ func (r record) UpdateValues(recordID uint64, rvs types.RecordValueSet) (err err
err = rvs.Walk(func(value *types.RecordValue) error {
value.RecordID = recordID
return r.db().Insert("compose_record_value", value)
})
return errors.Wrap(err, "could not insert record values")
}
func (r record) PartialUpdateValues(rvs ...*types.RecordValue) (err error) {
err = types.RecordValueSet(rvs).Walk(func(value *types.RecordValue) error {
return r.db().Replace("compose_record_value", value)
})

View File

@ -35,6 +35,7 @@ type RecordAPI interface {
ImportRun(context.Context, *request.RecordImportRun) (interface{}, error)
ImportProgress(context.Context, *request.RecordImportProgress) (interface{}, error)
Export(context.Context, *request.RecordExport) (interface{}, error)
Exec(context.Context, *request.RecordExec) (interface{}, error)
Create(context.Context, *request.RecordCreate) (interface{}, error)
Read(context.Context, *request.RecordRead) (interface{}, error)
Update(context.Context, *request.RecordUpdate) (interface{}, error)
@ -50,6 +51,7 @@ type Record struct {
ImportRun func(http.ResponseWriter, *http.Request)
ImportProgress func(http.ResponseWriter, *http.Request)
Export func(http.ResponseWriter, *http.Request)
Exec func(http.ResponseWriter, *http.Request)
Create func(http.ResponseWriter, *http.Request)
Read func(http.ResponseWriter, *http.Request)
Update func(http.ResponseWriter, *http.Request)
@ -179,6 +181,26 @@ func NewRecord(h RecordAPI) *Record {
resputil.JSON(w, value)
}
},
Exec: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewRecordExec()
if err := params.Fill(r); err != nil {
logger.LogParamError("Record.Exec", r, err)
resputil.JSON(w, err)
return
}
value, err := h.Exec(r.Context(), params)
if err != nil {
logger.LogControllerError("Record.Exec", r, err, params.Auditable())
resputil.JSON(w, err)
return
}
logger.LogControllerCall("Record.Exec", r, params.Auditable())
if !serveHTTP(value, w, r) {
resputil.JSON(w, value)
}
},
Create: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewRecordCreate()
@ -291,6 +313,7 @@ func (h Record) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http
r.Patch("/namespace/{namespaceID}/module/{moduleID}/record/import/{sessionID}", h.ImportRun)
r.Get("/namespace/{namespaceID}/module/{moduleID}/record/import/{sessionID}", h.ImportProgress)
r.Get("/namespace/{namespaceID}/module/{moduleID}/record/export{filename}.{ext}", h.Export)
r.Post("/namespace/{namespaceID}/module/{moduleID}/record/exec/{procedure}", h.Exec)
r.Post("/namespace/{namespaceID}/module/{moduleID}/record/", h.Create)
r.Get("/namespace/{namespaceID}/module/{moduleID}/record/{recordID}", h.Read)
r.Post("/namespace/{namespaceID}/module/{moduleID}/record/{recordID}", h.Update)

View File

@ -347,6 +347,28 @@ func (ctrl *Record) Export(ctx context.Context, r *request.RecordExport) (interf
}, nil
}
func (ctrl Record) Exec(ctx context.Context, r *request.RecordExec) (interface{}, error) {
aa := request.ProcedureArgs(r.Args)
switch r.Procedure {
case "organize":
return resputil.OK(), ctrl.record.With(ctx).Organize(
r.NamespaceID,
r.ModuleID,
aa.GetUint64("recordID"),
aa.Get("sortingField"),
aa.Get("sortingValue"),
aa.Get("sortingFilter"),
aa.Get("valueField"),
aa.Get("value"),
)
default:
return nil, errors.New("unknown procedure")
}
return nil, nil
}
func (ctrl Record) makePayload(ctx context.Context, m *types.Module, r *types.Record, err error) (*recordPayload, error) {
if err != nil || r == nil {
return nil, err

View File

@ -435,6 +435,65 @@ func (r *RecordExport) Fill(req *http.Request) (err error) {
var _ RequestFiller = NewRecordExport()
// Record exec request parameters
type RecordExec struct {
Procedure string
NamespaceID uint64 `json:",string"`
ModuleID uint64 `json:",string"`
Args []ProcedureArg
}
func NewRecordExec() *RecordExec {
return &RecordExec{}
}
func (r RecordExec) Auditable() map[string]interface{} {
var out = map[string]interface{}{}
out["procedure"] = r.Procedure
out["namespaceID"] = r.NamespaceID
out["moduleID"] = r.ModuleID
out["args"] = r.Args
return out
}
func (r *RecordExec) Fill(req *http.Request) (err error) {
if strings.ToLower(req.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(req.Body).Decode(r)
switch {
case err == io.EOF:
err = nil
case err != nil:
return errors.Wrap(err, "error parsing http request body")
}
}
if err = req.ParseForm(); err != nil {
return err
}
get := map[string]string{}
post := map[string]string{}
urlQuery := req.URL.Query()
for name, param := range urlQuery {
get[name] = string(param[0])
}
postVars := req.Form
for name, param := range postVars {
post[name] = string(param[0])
}
r.Procedure = chi.URLParam(req, "procedure")
r.NamespaceID = parseUInt64(chi.URLParam(req, "namespaceID"))
r.ModuleID = parseUInt64(chi.URLParam(req, "moduleID"))
return err
}
var _ RequestFiller = NewRecordExec()
// Record create request parameters
type RecordCreate struct {
Values types.RecordValueSet

View File

@ -12,6 +12,15 @@ import (
"github.com/pkg/errors"
)
type (
ProcedureArgs []ProcedureArg
ProcedureArg struct {
Name string `json:"name"`
Value string `json:"value"`
}
)
var truthy = regexp.MustCompile(`^\s*(t(rue)?|y(es)?|1)\s*$`)
func parseJSONTextWithErr(s string) (types.JSONText, error) {
@ -97,3 +106,19 @@ func is(s string, matches ...string) bool {
}
return false
}
func (args ProcedureArgs) GetUint64(name string) uint64 {
u, _ := strconv.ParseUint(args.Get(name), 10, 64)
return u
}
func (args ProcedureArgs) Get(name string) string {
name = strings.ToLower(name)
for _, arg := range args {
if strings.ToLower(arg.Name) == name {
return arg.Value
}
}
return ""
}

View File

@ -2,6 +2,7 @@ package service
import (
"context"
"fmt"
"regexp"
"strconv"
"time"
@ -69,6 +70,8 @@ type (
Update(record *types.Record) (*types.Record, error)
DeleteByID(namespaceID, recordID uint64) error
Organize(namespaceID, moduleID, recordID uint64, sortingField, sortingValue, sortingFilter, valueField, value string) error
}
Encoder interface {
@ -164,14 +167,14 @@ func (svc record) loadModule(namespaceID, moduleID uint64) (m *types.Module, err
return
}
if m.Fields, err = svc.moduleRepo.FindFields(m.ID); err != nil {
return
}
if !svc.ac.CanReadModule(svc.ctx, m) {
return nil, ErrNoReadPermissions.withStack()
}
if m.Fields, err = svc.moduleRepo.FindFields(m.ID); err != nil {
return
}
return
}
@ -202,6 +205,8 @@ func (svc record) Report(namespaceID, moduleID uint64, metrics, dimensions, filt
}
func (svc record) Find(filter types.RecordFilter) (set types.RecordSet, f types.RecordFilter, err error) {
filter.PageFilter.NormalizePerPageWithDefaults()
var m *types.Module
if m, err = svc.loadModule(filter.NamespaceID, filter.ModuleID); err != nil {
return
@ -357,9 +362,7 @@ func (svc record) Update(mod *types.Record) (r *types.Record, err error) {
return nil, ErrStaleData.withStack()
}
now := time.Now()
r.UpdatedAt = &now
r.UpdatedBy = auth.GetIdentityFromContext(svc.ctx).Identity()
svc.recordInfoUpdate(r)
if err = svc.copyChanges(m, mod, r); err != nil {
return
@ -390,6 +393,12 @@ func (svc record) Update(mod *types.Record) (r *types.Record, err error) {
})
}
func (svc record) recordInfoUpdate(r *types.Record) {
now := time.Now()
r.UpdatedAt = &now
r.UpdatedBy = auth.GetIdentityFromContext(svc.ctx).Identity()
}
func (svc record) DeleteByID(namespaceID, recordID uint64) (err error) {
if recordID == 0 {
return ErrInvalidID.withStack()
@ -433,6 +442,141 @@ func (svc record) DeleteByID(namespaceID, recordID uint64) (err error) {
return errors.Wrap(err, "unable to delete record")
}
// Organize - Record organizer
//
// Reorders records & sets field value
func (svc record) Organize(namespaceID, moduleID, recordID uint64, sField, sValue, sFilter, vField, vValue string) error {
var (
_, module, record, err = svc.loadCombo(namespaceID, moduleID, recordID)
recordValues = types.RecordValueSet{}
reorderingRecords bool
)
if err != nil {
return err
}
if !svc.ac.CanUpdateRecord(svc.ctx, module) {
return ErrNoUpdatePermissions.withStack()
}
if sField != "" {
reorderingRecords = true
if !regexp.MustCompile(`^[0-9]+$`).MatchString(sValue) {
return errors.Errorf("expecting number for sorting position %q", sField)
}
// Check field existence and permissions
// check if numeric -- we can not reorder on any other field type
sf := module.Fields.FindByName(sField)
if sf == nil {
return errors.Errorf("no such field %q", sField)
}
if !sf.IsNumeric() {
return errors.Errorf("can not reorder on non numeric field %q", sField)
}
if sf.Multi {
return errors.Errorf("can not reorder on multi-value field %q", sField)
}
if !svc.ac.CanUpdateRecordValue(svc.ctx, sf) {
return ErrNoUpdatePermissions.withStack()
}
// Set new position
recordValues = recordValues.Set(&types.RecordValue{
RecordID: recordID,
Name: sField,
Value: sValue,
})
}
if vField != "" {
// Check field existence and permissions
vf := module.Fields.FindByName(vField)
if vf == nil {
return errors.Errorf("no such field %q", vField)
}
if vf.Multi {
return errors.Errorf("can not update multi-value field %q", sField)
}
if !svc.ac.CanUpdateRecordValue(svc.ctx, vf) {
return ErrNoUpdatePermissions.withStack()
}
// Set new value
recordValues = recordValues.Set(&types.RecordValue{
RecordID: recordID,
Name: vField,
Value: vValue,
})
}
return svc.db.Transaction(func() (err error) {
if len(recordValues) > 0 {
svc.recordInfoUpdate(record)
if _, err = svc.recordRepo.Update(record); err != nil {
return
}
if err = svc.recordRepo.PartialUpdateValues(recordValues...); err != nil {
return
}
}
if reorderingRecords {
var (
set types.RecordSet
recordOrderPlace uint64
)
// If we already have filter, wrap it in parenthesis
if sFilter != "" {
sFilter = fmt.Sprintf("(%s) AND ", sFilter)
}
if recordOrderPlace, err = strconv.ParseUint(sValue, 0, 64); err != nil {
return
}
// Assemble record filter:
// We are interested only in records that have value of a sorting field greater than
// the place we're moving our record to.
// and sort the set with sorting field
set, _, err = svc.recordRepo.Find(module, types.RecordFilter{
Filter: fmt.Sprintf("%s(%s >= %d)", sFilter, sField, recordOrderPlace),
Sort: sField,
})
if err != nil {
return
}
// Update value on each record
return set.Walk(func(r *types.Record) error {
recordOrderPlace++
// Update each and every set
return svc.recordRepo.PartialUpdateValues(&types.RecordValue{
RecordID: r.ID,
Name: sField,
Value: strconv.FormatUint(recordOrderPlace, 10),
})
})
}
return
})
}
// loadCombo Loads everything we need for record manipulation
//
// Loads namespace, module, record and set of triggers.

View File

@ -74,13 +74,13 @@ func (set RecordValueSet) Has(name string, place uint) bool {
return false
}
func (meta *RecordValueSet) Scan(value interface{}) error {
func (set *RecordValueSet) Scan(value interface{}) error {
//lint:ignore S1034 This typecast is intentional, we need to get []byte out of a []uint8
switch value.(type) {
case nil:
*meta = RecordValueSet{}
*set = RecordValueSet{}
case []uint8:
if err := json.Unmarshal(value.([]byte), meta); err != nil {
if err := json.Unmarshal(value.([]byte), set); err != nil {
return errors.Wrapf(err, "Can not scan '%v' into RecordValueSet", value)
}
}
@ -88,6 +88,6 @@ func (meta *RecordValueSet) Scan(value interface{}) error {
return nil
}
func (meta RecordValueSet) Value() (driver.Value, error) {
return json.Marshal(meta)
func (set RecordValueSet) Value() (driver.Value, error) {
return json.Marshal(set)
}

View File

@ -960,6 +960,7 @@ Compose records
| `PATCH` | `/namespace/{namespaceID}/module/{moduleID}/record/import/{sessionID}` | Run record import |
| `GET` | `/namespace/{namespaceID}/module/{moduleID}/record/import/{sessionID}` | Get import progress |
| `GET` | `/namespace/{namespaceID}/module/{moduleID}/record/export{filename}.{ext}` | Exports records that match |
| `POST` | `/namespace/{namespaceID}/module/{moduleID}/record/exec/{procedure}` | Executes server-side procedure over one or more module records |
| `POST` | `/namespace/{namespaceID}/module/{moduleID}/record/` | Create record in module section |
| `GET` | `/namespace/{namespaceID}/module/{moduleID}/record/{recordID}` | Read records by ID from module section |
| `POST` | `/namespace/{namespaceID}/module/{moduleID}/record/{recordID}` | Update records in module section |
@ -1072,6 +1073,23 @@ Compose records
| namespaceID | uint64 | PATH | Namespace ID | N/A | YES |
| moduleID | uint64 | PATH | Module ID | N/A | YES |
## Executes server-side procedure over one or more module records
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/namespace/{namespaceID}/module/{moduleID}/record/exec/{procedure}` | HTTP/S | POST | |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| procedure | string | PATH | Name of procedure to execute | N/A | YES |
| namespaceID | uint64 | PATH | Namespace ID | N/A | YES |
| moduleID | uint64 | PATH | Module ID | N/A | YES |
| args | []ProcedureArg | POST | Procedure arguments | N/A | NO |
## Create record in module section
#### Method

View File

@ -121,6 +121,11 @@ func newHelper(t *testing.T) helper {
return h
}
// Returns context w/ security details
func (h helper) secCtx() context.Context {
return auth.SetIdentityToContext(context.Background(), h.cUser)
}
// apitest basics, initialize, set handler, add auth
func (h helper) apiInit() *apitest.APITest {
InitApp()

View File

@ -17,10 +17,10 @@ func (h helper) repoModule() repository.ModuleRepository {
return repository.Module(context.Background(), db())
}
func (h helper) repoMakeModule(ns *types.Namespace, name string) *types.Module {
func (h helper) repoMakeModule(ns *types.Namespace, name string, ff ...*types.ModuleField) *types.Module {
m, err := h.
repoModule().
Create(&types.Module{Name: name, NamespaceID: ns.ID})
Create(&types.Module{Name: name, NamespaceID: ns.ID, Fields: ff})
h.a.NoError(err)
return m

View File

@ -0,0 +1,161 @@
package compose
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"testing"
"github.com/steinfletcher/apitest"
"github.com/cortezaproject/corteza-server/compose/rest/request"
"github.com/cortezaproject/corteza-server/compose/service"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/rh"
"github.com/cortezaproject/corteza-server/tests/helpers"
)
func (h helper) apiSendRecordExec(nsID, modID uint64, proc string, args []request.ProcedureArg) *apitest.Response {
payload, err := json.Marshal(request.RecordExec{Args: args})
h.a.NoError(err)
return h.apiInit().
Post(fmt.Sprintf("/namespace/%d/module/%d/record/exec/%s", nsID, modID, proc)).
JSON(string(payload)).
Expect(h.t)
}
func TestRecordExecUnknownProcedure(t *testing.T) {
h := newHelper(t)
h.apiInit().
Post("/namespace/0/module/0/record/exec/test-unexisting-proc").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("unknown procedure")).
End()
}
func TestRecordExec(t *testing.T) {
h := newHelper(t)
h.allow(types.ModulePermissionResource.AppendWildcard(), "record.update")
module := h.repoMakeRecordModuleWithFields(
"record testing module",
&types.ModuleField{Name: "position", Kind: "Number"},
&types.ModuleField{Name: "handle"},
&types.ModuleField{Name: "category"},
)
makeRecord := func(position int, handle, cat string) *types.Record {
return h.repoMakeRecord(module,
&types.RecordValue{Name: "position", Value: strconv.Itoa(position)},
&types.RecordValue{Name: "handle", Value: handle},
&types.RecordValue{Name: "category", Value: cat},
)
}
assertSort := func(expectedHandles string) {
// Using record service for fetching to avoid value pre-fetching etc..
set, _, err := service.DefaultRecord.With(h.secCtx()).Find(types.RecordFilter{
ModuleID: module.ID,
NamespaceID: module.NamespaceID,
Sort: "position ASC",
PageFilter: rh.PageFilter{},
})
h.a.NoError(err)
h.a.NotNil(set)
actualHandles := ""
_ = set.Walk(func(r *types.Record) error {
v := r.Values.FilterByName("handle")
if len(v) == 1 {
actualHandles += v[0].Value
} else {
actualHandles += strconv.Itoa(len(v))
}
return nil
})
h.a.Equal(expectedHandles, actualHandles)
}
var (
aRec = makeRecord(1, "a", "CAT1")
bRec = makeRecord(2, "b", "CAT1")
cRec = makeRecord(3, "c", "CAT1")
dRec = makeRecord(4, "d", "CAT2")
eRec = makeRecord(5, "e", "CAT2")
fRec = makeRecord(6, "f", "CAT2")
gRec = makeRecord(7, "g", "CAT3")
hRec = makeRecord(8, "h", "CAT3")
iRec = makeRecord(9, "i", "CAT3")
)
// map handle to record ID so we can use it for reordering
rr := map[string]string{
"a": strconv.FormatUint(aRec.ID, 10),
"b": strconv.FormatUint(bRec.ID, 10),
"c": strconv.FormatUint(cRec.ID, 10),
"d": strconv.FormatUint(dRec.ID, 10),
"e": strconv.FormatUint(eRec.ID, 10),
"f": strconv.FormatUint(fRec.ID, 10),
"g": strconv.FormatUint(gRec.ID, 10),
"h": strconv.FormatUint(hRec.ID, 10),
"i": strconv.FormatUint(iRec.ID, 10),
}
assertSort("abcdefghi")
h.apiSendRecordExec(module.NamespaceID, module.ID, "organize", request.ProcedureArgs{
{"recordID", rr["a"]},
{"sortingField", "position"},
{"sortingValue", "5"}}).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
assertSort("bcdeafghi")
h.apiSendRecordExec(module.NamespaceID, module.ID, "organize", request.ProcedureArgs{
{"recordID", rr["i"]},
{"sortingField", "position"},
{"sortingValue", "0"}}).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
assertSort("ibcdeafgh")
h.apiSendRecordExec(module.NamespaceID, module.ID, "organize", request.ProcedureArgs{
{"recordID", rr["b"]},
{"sortingFilter", "category = 'CAT1'"},
{"sortingField", "position"},
{"sortingValue", "5"}}).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
assertSort("idecbfgah")
h.apiSendRecordExec(module.NamespaceID, module.ID, "organize", request.ProcedureArgs{
{"recordID", rr["b"]},
{"valueField", "category"},
{"value", "CAT2"},
{"sortingValue", "5"}}).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
End()
assertSort("idecbfgah")
rsv, err := h.repoRecord().LoadValues([]string{"category"}, []uint64{bRec.ID})
h.a.NoError(err)
h.a.NotNil(rsv)
h.a.Len(rsv.FilterByName("category"), 1)
h.a.Equal("CAT2", rsv.FilterByName("category")[0].Value)
}

View File

@ -18,45 +18,40 @@ func (h helper) repoRecord() repository.RecordRepository {
return repository.Record(context.Background(), db())
}
func (h helper) repoMakeRecordModuleWithFields(name string) *types.Module {
func (h helper) repoMakeRecordModuleWithFields(name string, ff ...*types.ModuleField) *types.Module {
namespace := h.repoMakeNamespace("record testing namespace")
h.allow(types.NamespacePermissionResource.AppendWildcard(), "read")
h.allow(types.ModulePermissionResource.AppendWildcard(), "read")
h.allow(types.ModulePermissionResource.AppendWildcard(), "record.read")
m, err := h.
repoModule().
Create(&types.Module{
Name: name,
NamespaceID: namespace.ID,
Fields: types.ModuleFieldSet{
&types.ModuleField{
Name: "name",
},
&types.ModuleField{
Name: "email",
},
&types.ModuleField{
Name: "options",
Multi: true,
},
&types.ModuleField{
Name: "description",
},
&types.ModuleField{
Name: "another_record",
Kind: "Record",
},
if len(ff) == 0 {
// Default fields
ff = types.ModuleFieldSet{
&types.ModuleField{
Name: "name",
},
})
&types.ModuleField{
Name: "email",
},
&types.ModuleField{
Name: "options",
Multi: true,
},
&types.ModuleField{
Name: "description",
},
&types.ModuleField{
Name: "another_record",
Kind: "Record",
},
}
}
h.a.NoError(err)
return m
return h.repoMakeModule(namespace, name, ff...)
}
func (h helper) repoMakeRecord(module *types.Module, name string) *types.Record {
func (h helper) repoMakeRecord(module *types.Module, rvs ...*types.RecordValue) *types.Record {
record, err := h.
repoRecord().
Create(&types.Record{
@ -68,6 +63,9 @@ func (h helper) repoMakeRecord(module *types.Module, name string) *types.Record
})
h.a.NoError(err)
err = h.repoRecord().UpdateValues(record.ID, rvs)
h.a.NoError(err)
return record
}
@ -75,7 +73,7 @@ func TestRecordRead(t *testing.T) {
h := newHelper(t)
module := h.repoMakeRecordModuleWithFields("record testing module")
record := h.repoMakeRecord(module, "some-record")
record := h.repoMakeRecord(module)
h.apiInit().
Get(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)).
@ -91,8 +89,8 @@ func TestRecordList(t *testing.T) {
module := h.repoMakeRecordModuleWithFields("record testing module")
h.repoMakeRecord(module, "app")
h.repoMakeRecord(module, "app")
h.repoMakeRecord(module)
h.repoMakeRecord(module)
h.apiInit().
Get(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID)).
@ -109,7 +107,7 @@ func TestRecordCreateForbidden(t *testing.T) {
h.apiInit().
Post(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID)).
FormData("name", "some-record").
FormData("name").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("compose.service.NoCreatePermissions")).
@ -124,7 +122,7 @@ func TestRecordCreate(t *testing.T) {
h.apiInit().
Post(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID)).
FormData("name", "some-record").
FormData("name").
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
@ -135,7 +133,7 @@ func TestRecordUpdateForbidden(t *testing.T) {
h := newHelper(t)
module := h.repoMakeRecordModuleWithFields("record testing module")
record := h.repoMakeRecord(module, "some-record")
record := h.repoMakeRecord(module)
h.apiInit().
Post(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)).
@ -150,7 +148,7 @@ func TestRecordUpdate(t *testing.T) {
h := newHelper(t)
module := h.repoMakeRecordModuleWithFields("record testing module")
record := h.repoMakeRecord(module, "some-record")
record := h.repoMakeRecord(module)
h.allow(types.ModulePermissionResource.AppendWildcard(), "record.update")
h.apiInit().
@ -171,7 +169,7 @@ func TestRecordDeleteForbidden(t *testing.T) {
h := newHelper(t)
module := h.repoMakeRecordModuleWithFields("record testing module")
record := h.repoMakeRecord(module, "some-record")
record := h.repoMakeRecord(module)
h.apiInit().
Delete(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)).
@ -185,7 +183,7 @@ func TestRecordDelete(t *testing.T) {
h := newHelper(t)
module := h.repoMakeRecordModuleWithFields("record testing module")
record := h.repoMakeRecord(module, "some-record")
record := h.repoMakeRecord(module)
h.allow(types.ModulePermissionResource.AppendWildcard(), "record.delete")