Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
1781 lines
52 KiB
Go
1781 lines
52 KiB
Go
package compose
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/cortezaproject/corteza/server/compose/dalutils"
|
|
"github.com/cortezaproject/corteza/server/compose/service/values"
|
|
"github.com/cortezaproject/corteza/server/compose/types"
|
|
"github.com/cortezaproject/corteza/server/pkg/id"
|
|
systemService "github.com/cortezaproject/corteza/server/system/service"
|
|
"github.com/cortezaproject/corteza/server/tests/helpers"
|
|
"github.com/steinfletcher/apitest"
|
|
jsonpath "github.com/steinfletcher/apitest-jsonpath"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func (h helper) clearRecords() {
|
|
h.clearNamespaces()
|
|
h.clearModules()
|
|
h.noError(truncateRecords(context.Background()))
|
|
}
|
|
|
|
type (
|
|
rImportSession struct {
|
|
Response struct {
|
|
SessionID string `json:"sessionID"`
|
|
} `json:"response"`
|
|
}
|
|
)
|
|
|
|
func (h helper) makeRecordModuleWithFieldsOnNs(name string, namespace *types.Namespace, ff ...*types.ModuleField) *types.Module {
|
|
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
|
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "read")
|
|
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "read")
|
|
helpers.AllowMe(h, types.ModuleFieldRbacResource(0, 0, 0), "record.value.read")
|
|
helpers.AllowMe(h, types.ModuleFieldRbacResource(0, 0, 0), "record.value.update")
|
|
|
|
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",
|
|
},
|
|
}
|
|
}
|
|
|
|
return h.makeModule(namespace, name, ff...)
|
|
}
|
|
|
|
func (h helper) repoMakeRecordModuleWithFields(name string, ff ...*types.ModuleField) *types.Module {
|
|
namespace := h.makeNamespace("record testing namespace")
|
|
|
|
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
|
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "read")
|
|
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "read")
|
|
helpers.AllowMe(h, types.ModuleFieldRbacResource(0, 0, 0), "record.value.read", "record.value.update")
|
|
|
|
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",
|
|
},
|
|
}
|
|
}
|
|
|
|
return h.makeModule(namespace, name, ff...)
|
|
}
|
|
|
|
func (h helper) repoMakeRecordModuleWithFieldsRequired(name string, ff ...*types.ModuleField) *types.Module {
|
|
namespace := h.makeNamespace("record testing namespace")
|
|
|
|
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
|
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "read")
|
|
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "read")
|
|
helpers.AllowMe(h, types.ModuleFieldRbacResource(0, 0, 0), "record.value.read", "record.value.update")
|
|
|
|
if len(ff) == 0 {
|
|
// Default fields
|
|
ff = types.ModuleFieldSet{
|
|
&types.ModuleField{
|
|
Name: "name",
|
|
Required: true,
|
|
},
|
|
&types.ModuleField{
|
|
Name: "email",
|
|
},
|
|
&types.ModuleField{
|
|
Name: "options",
|
|
Multi: true,
|
|
},
|
|
&types.ModuleField{
|
|
Name: "description",
|
|
},
|
|
&types.ModuleField{
|
|
Name: "another_record",
|
|
Kind: "Record",
|
|
},
|
|
}
|
|
}
|
|
|
|
return h.makeModule(namespace, name, ff...)
|
|
}
|
|
|
|
func (h helper) makeRecord(module *types.Module, rvs ...*types.RecordValue) *types.Record {
|
|
recID := id.Next()
|
|
for _, rv := range rvs {
|
|
rv.RecordID = recID
|
|
}
|
|
|
|
rec := &types.Record{
|
|
ID: recID,
|
|
CreatedAt: time.Now(),
|
|
ModuleID: module.ID,
|
|
NamespaceID: module.NamespaceID,
|
|
// Passing the current owner in here since the tests (who care about this)
|
|
// rely on it being set to something valid.
|
|
OwnedBy: h.cUser.ID,
|
|
|
|
// We are directly storing the record values here, so ensure
|
|
// everything is formatted in the same manner as it would be
|
|
// when stored through the service
|
|
Values: values.Formatter().Run(module, rvs),
|
|
}
|
|
rec.SetModule(module)
|
|
|
|
h.noError(dalutils.ComposeRecordCreate(context.Background(), defDal, module, rec))
|
|
|
|
return rec
|
|
}
|
|
|
|
func (h helper) lookupRecordByID(module *types.Module, ID uint64) *types.Record {
|
|
res, err := dalutils.ComposeRecordsFind(context.Background(), defDal, module, ID)
|
|
h.noError(err)
|
|
return res
|
|
}
|
|
|
|
func TestRecordRead(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module")
|
|
record := h.makeRecord(module)
|
|
|
|
h.apiInit().
|
|
Get(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
Assert(jsonpath.Equal(`$.response.recordID`, fmt.Sprintf("%d", record.ID))).
|
|
End()
|
|
}
|
|
|
|
func TestRecordList(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module")
|
|
helpers.AllowMe(h, module.RbacResource(), "records.search")
|
|
|
|
h.makeRecord(module)
|
|
h.makeRecord(module)
|
|
|
|
h.apiInit().
|
|
Get(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
Assert(jsonpath.Len(`$.response.set`, 2)).
|
|
End()
|
|
}
|
|
|
|
func TestRecordListWithTotal(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module")
|
|
helpers.AllowMe(h, module.RbacResource(), "records.search")
|
|
|
|
for i := 0; i < 7; i++ {
|
|
h.makeRecord(module, &types.RecordValue{Name: "name", Value: fmt.Sprintf("%d", i+1)})
|
|
}
|
|
|
|
h.apiInit().
|
|
Get(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID)).
|
|
Query("incTotal", "true").
|
|
Query("sort", "name DESC").
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
Assert(jsonpath.Equal(`$.response.filter.total`, float64(7))).
|
|
End()
|
|
}
|
|
|
|
func TestRecordListWithPaginationAndSorting(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module")
|
|
helpers.AllowMe(h, module.RbacResource(), "records.search")
|
|
|
|
var aux = struct {
|
|
Response struct {
|
|
Filter struct {
|
|
NextPage *string
|
|
PrevPage *string
|
|
PageNavigation []struct {
|
|
Page int
|
|
Items int
|
|
Cursor *string
|
|
}
|
|
}
|
|
}
|
|
}{}
|
|
|
|
for i := 0; i < 7; i++ {
|
|
h.makeRecord(module, &types.RecordValue{Name: "name", Value: fmt.Sprintf("%d", i+1)})
|
|
}
|
|
|
|
// 1st page
|
|
h.apiInit().
|
|
Get(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID)).
|
|
Query("incTotal", "true").
|
|
Query("incPageNavigation", "true").
|
|
Query("limit", "2").
|
|
Query("sort", "name DESC").
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
Assert(jsonpath.Equal(`$.response.set[0].values[0].value`, "7")).
|
|
Assert(jsonpath.Equal(`$.response.set[1].values[0].value`, "6")).
|
|
Assert(jsonpath.Equal(`$.response.filter.total`, float64(7))).
|
|
Assert(jsonpath.Present(`$.response.filter.pageNavigation`)).
|
|
Assert(jsonpath.Len(`$.response.filter.pageNavigation`, 4)).
|
|
End().
|
|
JSON(&aux)
|
|
|
|
h.a.Len(aux.Response.Filter.PageNavigation, 4)
|
|
h.a.NotNil(aux.Response.Filter.PageNavigation[1].Cursor)
|
|
|
|
// 2nd page
|
|
h.apiInit().
|
|
Get(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID)).
|
|
Query("incTotal", "false").
|
|
Query("incPageNavigation", "false").
|
|
Query("limit", "2").
|
|
Query("pageCursor", *aux.Response.Filter.PageNavigation[1].Cursor).
|
|
Query("sort", "name DESC").
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
Assert(jsonpath.Equal(`$.response.set[0].values[0].value`, "5")).
|
|
Assert(jsonpath.Equal(`$.response.set[1].values[0].value`, "4")).
|
|
Assert(jsonpath.NotPresent(`$.response.filter.total`)).
|
|
Assert(jsonpath.NotPresent(`$.response.filter.pageNavigation`)).
|
|
End()
|
|
}
|
|
|
|
func TestRecordListForbiddenRecords(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module")
|
|
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "records.search")
|
|
helpers.DenyMe(h, types.RecordRbacResource(0, 0, 0), "read")
|
|
|
|
h.makeRecord(module)
|
|
h.makeRecord(module)
|
|
|
|
h.apiInit().
|
|
Get(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID)).
|
|
Query("incTotal", "true").
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
// not present because omitted when empty
|
|
Assert(jsonpath.NotPresent(`$.response.filter.total`)).
|
|
End()
|
|
}
|
|
|
|
func TestRecordListForbiddenFields(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module")
|
|
helpers.AllowMe(h, module.RbacResource(), "records.create", "records.search")
|
|
helpers.DenyMe(h, types.ModuleFieldRbacResource(0, 0, module.Fields[0].ID), "record.value.read")
|
|
|
|
h.makeRecord(module, &types.RecordValue{Name: "name", Value: "v_name_0"}, &types.RecordValue{Name: "email", Value: "v_email_0"})
|
|
h.makeRecord(module, &types.RecordValue{Name: "name", Value: "v_name_1"}, &types.RecordValue{Name: "email", Value: "v_email_1"})
|
|
|
|
h.apiInit().
|
|
Get(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID)).
|
|
Query("incTotal", "true").
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
Assert(jsonpath.Equal(`$.response.filter.total`, float64(2))).
|
|
Assert(jsonpath.Len(`$.response.set`, 2)).
|
|
// rec 1
|
|
Assert(jsonpath.Len(`$.response.set[0].values`, 1)).
|
|
Assert(jsonpath.Equal(`$.response.set[0].values[0].name`, "email")).
|
|
// rec 2
|
|
Assert(jsonpath.Len(`$.response.set[1].values`, 1)).
|
|
Assert(jsonpath.Equal(`$.response.set[1].values[0].name`, "email")).
|
|
End()
|
|
}
|
|
|
|
func TestRecordCreateForbidden(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module")
|
|
|
|
h.apiInit().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID)).
|
|
Header("Accept", "application/json").
|
|
JSON(fmt.Sprintf(`{"values": [{"name": "name", "value": "val"}]}`)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertError("record.errors.notAllowedToCreate")).
|
|
End()
|
|
}
|
|
|
|
func TestRecordCreate(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module")
|
|
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "record.create")
|
|
helpers.AllowMe(h, types.ModuleFieldRbacResource(0, 0, 0), "record.value.update")
|
|
|
|
h.apiInit().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID)).
|
|
JSON(fmt.Sprintf(`{"values": [{"name": "name", "value": "val"}]}`)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
End()
|
|
}
|
|
|
|
func TestRecordCreateForbidden_forbiddenFields(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module",
|
|
&types.ModuleField{Name: "f1", Kind: "String"},
|
|
&types.ModuleField{Name: "f2", Kind: "String"},
|
|
)
|
|
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "record.create")
|
|
helpers.DenyMe(h, module.Fields[1].RbacResource(), "record.value.update")
|
|
|
|
h.apiInit().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID)).
|
|
JSON(fmt.Sprintf(`{"values": [{"name": "f1", "value": "f1.v1"}, {"name": "f2", "value": "f2.v1"}]}`)).
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertErrorP("1 issue(s) found")).
|
|
End()
|
|
}
|
|
|
|
func TestRecordCreate_forbiddenFields(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module",
|
|
&types.ModuleField{Name: "f1", Kind: "String"},
|
|
&types.ModuleField{Name: "f2", Kind: "String"},
|
|
)
|
|
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "record.create")
|
|
helpers.DenyMe(h, module.Fields[1].RbacResource(), "record.value.update")
|
|
|
|
h.apiInit().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID)).
|
|
JSON(fmt.Sprintf(`{"values": [{"name": "f1", "value": "f1.v1"}]}`)).
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
End()
|
|
}
|
|
|
|
func TestRecordCreate_xss(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
var (
|
|
ns = h.makeNamespace("some-namespace")
|
|
mod = h.makeModule(ns, "some-module",
|
|
&types.ModuleField{
|
|
Kind: "String",
|
|
Name: "dummy",
|
|
},
|
|
&types.ModuleField{
|
|
Kind: "String",
|
|
Name: "dummyRichTextBox",
|
|
Options: map[string]interface{}{
|
|
"useRichTextEditor": true,
|
|
},
|
|
},
|
|
)
|
|
)
|
|
|
|
helpers.AllowMe(h, ns.RbacResource(), "read")
|
|
helpers.AllowMe(h, mod.RbacResource(), "read")
|
|
helpers.AllowMe(h, mod.RbacResource(), "record.create")
|
|
helpers.AllowMe(h, mod.Fields[0].RbacResource(), "record.value.update")
|
|
helpers.AllowMe(h, mod.Fields[1].RbacResource(), "record.value.update")
|
|
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "update")
|
|
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "read")
|
|
|
|
t.Run("create with rich text fields", func(t *testing.T) {
|
|
var (
|
|
req = require.New(t)
|
|
|
|
payload = struct {
|
|
Response *types.Record
|
|
}{}
|
|
|
|
rec = &types.Record{
|
|
Values: types.RecordValueSet{
|
|
&types.RecordValue{Name: "dummyRichTextBox", Value: "<img src=x onerror=alert(11111)>test"},
|
|
&types.RecordValue{Name: "dummy", Value: "simple-text"},
|
|
},
|
|
}
|
|
)
|
|
|
|
h.apiInit().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/", ns.ID, mod.ID)).
|
|
JSON(helpers.JSON(rec)).
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(jsonpath.Present(`$.response.values[? @.name=="dummyRichTextBox"]`)).
|
|
Assert(jsonpath.Present(`$.response.values[? @.name=="dummy"]`)).
|
|
Assert(jsonpath.Present(`$.response.values[? @.value=="simple-text"]`)).
|
|
Assert(jsonpath.Present(`$.response.values[? @.value=="<img src=\"x\">test"]`)).
|
|
End().
|
|
JSON(&payload)
|
|
|
|
req.NotNil(payload.Response)
|
|
req.NotZero(payload.Response.ID)
|
|
})
|
|
}
|
|
|
|
func TestRecordCreateWithErrors(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
fields := types.ModuleFieldSet{
|
|
&types.ModuleField{
|
|
ID: id.Next(),
|
|
Name: "name",
|
|
},
|
|
&types.ModuleField{
|
|
ID: id.Next(),
|
|
Name: "required",
|
|
Required: true,
|
|
},
|
|
}
|
|
module := h.repoMakeRecordModuleWithFields("record testing module", fields...)
|
|
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "record.create")
|
|
helpers.AllowMe(h, types.ModuleFieldRbacResource(0, 0, 0), "record.value.update")
|
|
|
|
h.apiInit().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID)).
|
|
JSON(fmt.Sprintf(`{"values": [{"name": "name", "value": "val"}]}`)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Assert(helpers.AssertRecordValueError(
|
|
&types.RecordValueError{
|
|
Kind: "empty",
|
|
Message: "record-field.errors.empty",
|
|
Meta: map[string]interface{}{"field": "required"},
|
|
},
|
|
)).
|
|
End()
|
|
}
|
|
|
|
func TestRecordUpdateForbidden(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module")
|
|
record := h.makeRecord(module)
|
|
|
|
h.apiInit().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)).
|
|
Header("Accept", "application/json").
|
|
JSON(fmt.Sprintf(`{"values": [{"name": "name", "value": "changed-val"}]}`)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertError("record.errors.notAllowedToUpdate")).
|
|
End()
|
|
}
|
|
|
|
func TestRecordUpdate(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module")
|
|
record := h.makeRecord(module)
|
|
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "update")
|
|
|
|
h.apiInit().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)).
|
|
JSON(fmt.Sprintf(`{"values": [{"name": "name", "value": "changed-val"}]}`)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
End()
|
|
|
|
r := h.lookupRecordByID(module, record.ID)
|
|
h.a.NotNil(r)
|
|
}
|
|
|
|
func TestRecordUpdate_missingField(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module",
|
|
&types.ModuleField{Name: "f1", Kind: "String"},
|
|
&types.ModuleField{Name: "f2", Kind: "String"},
|
|
)
|
|
record := h.makeRecord(module,
|
|
&types.RecordValue{Name: "f1", Value: "f1.v1"},
|
|
&types.RecordValue{Name: "f2", Value: "f2.v1"},
|
|
)
|
|
_ = record
|
|
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
|
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "update")
|
|
|
|
api := h.apiInit()
|
|
|
|
// delete f2
|
|
api.
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d", module.NamespaceID, module.ID)).
|
|
JSON(fmt.Sprintf(`{"name": "%s", "handle": "%s", "fields": [{ "name": "f1", "kind": "String" }]}`, module.Name, module.Handle)).
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
End()
|
|
|
|
// update f1
|
|
api.
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)).
|
|
JSON(fmt.Sprintf(`{"values": [{"name": "f1", "value": "f1.v1 (edited)"}]}`)).
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
End()
|
|
|
|
r := h.lookupRecordByID(module, record.ID)
|
|
h.a.NotNil(r)
|
|
h.a.Len(r.Values, 1)
|
|
h.a.Equal("f1", r.Values[0].Name)
|
|
h.a.Equal("f1.v1 (edited)", r.Values[0].Value)
|
|
}
|
|
|
|
func TestRecordUpdateForbidden_forbiddenFields(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module",
|
|
&types.ModuleField{Name: "f1", Kind: "String"},
|
|
&types.ModuleField{Name: "f2", Kind: "String"},
|
|
)
|
|
record := h.makeRecord(module,
|
|
&types.RecordValue{Name: "f1", Value: "f1.v1"},
|
|
&types.RecordValue{Name: "f2", Value: "f2.v1"},
|
|
)
|
|
|
|
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "update")
|
|
helpers.DenyMe(h, module.Fields[1].RbacResource(), "record.value.update")
|
|
|
|
h.apiInit().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)).
|
|
JSON(fmt.Sprintf(`{"values": [{"name": "f1", "value": "f1.v1"}, {"name": "f2", "value": "f2.v1 (edited)"}]}`)).
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertError("1 issue(s) found")).
|
|
End()
|
|
|
|
r := h.lookupRecordByID(module, record.ID)
|
|
h.a.NotNil(r)
|
|
h.a.Equal("f2.v1", r.Values.FilterByName("f2")[0].Value)
|
|
}
|
|
|
|
func TestRecordUpdate_forbiddenFields(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module",
|
|
&types.ModuleField{Name: "f1", Kind: "String"},
|
|
&types.ModuleField{Name: "f2", Kind: "String"},
|
|
|
|
// we'll test all kinds of boolean inputs
|
|
&types.ModuleField{Name: "f-b-f-n", Kind: "Bool"},
|
|
&types.ModuleField{Name: "f-b-f-m", Kind: "Bool"},
|
|
&types.ModuleField{Name: "f-b-f-e", Kind: "Bool"},
|
|
&types.ModuleField{Name: "f-b-f-z", Kind: "Bool"},
|
|
&types.ModuleField{Name: "f-b-t-n", Kind: "Bool"},
|
|
&types.ModuleField{Name: "f-b-t-m", Kind: "Bool"},
|
|
&types.ModuleField{Name: "f-b-t-v", Kind: "Bool"},
|
|
)
|
|
record := h.makeRecord(module,
|
|
&types.RecordValue{Name: "f1", Value: "f1.v1"},
|
|
&types.RecordValue{Name: "f2", Value: "f2.v1"},
|
|
&types.RecordValue{Name: "f-b-f-n", Value: "0"}, // no-value
|
|
&types.RecordValue{Name: "f-b-f-e", Value: "0"}, // empty
|
|
&types.RecordValue{Name: "f-b-f-z", Value: "0"}, // zero
|
|
&types.RecordValue{Name: "f-b-t-n", Value: "1"}, // no-value
|
|
&types.RecordValue{Name: "f-b-t-v", Value: "1"}, // value
|
|
)
|
|
helpers.AllowMe(h, types.RecordRbacResource(0, 0, record.ID), "update")
|
|
helpers.AllowMe(h, module.Fields[0].RbacResource(), "record.value.update")
|
|
helpers.DenyMe(h, types.ModuleFieldRbacResource(0, record.ModuleID, 0), "record.value.update")
|
|
|
|
h.apiInit().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)).
|
|
JSON(fmt.Sprintf(`{"values": [
|
|
{"name": "f1", "value": "f1.v1"},
|
|
{"name": "f2", "value": "f2.v1"},
|
|
{"name": "f-b-f-n"},
|
|
{"name": "f-b-f-e", "value": ""},
|
|
{"name": "f-b-f-z", "value": "0"},
|
|
{"name": "f-b-t-v", "value": "1"}
|
|
]}`)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
End()
|
|
|
|
r := h.lookupRecordByID(module, record.ID)
|
|
h.a.NotNil(r)
|
|
h.a.Equal("f2.v1", r.Values.FilterByName("f2")[0].Value)
|
|
h.a.Equal("", r.Values.FilterByName("f-b-f-n")[0].Value)
|
|
h.a.Equal("", r.Values.FilterByName("f-b-f-e")[0].Value)
|
|
h.a.Equal("", r.Values.FilterByName("f-b-f-z")[0].Value)
|
|
h.a.Equal("1", r.Values.FilterByName("f-b-t-n")[0].Value)
|
|
h.a.Equal("1", r.Values.FilterByName("f-b-t-v")[0].Value)
|
|
}
|
|
|
|
func TestRecordUpdate_refUnchanged(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
namespace := h.makeNamespace("record testing namespace")
|
|
|
|
// mods
|
|
mRef := h.makeRecordModuleWithFieldsOnNs("record testing module", namespace)
|
|
module := h.makeRecordModuleWithFieldsOnNs("record testing module", namespace,
|
|
&types.ModuleField{
|
|
Name: "name",
|
|
Kind: "String",
|
|
},
|
|
&types.ModuleField{
|
|
Name: "ref",
|
|
Kind: "Record",
|
|
Options: types.ModuleFieldOptions{
|
|
"moduleID": strconv.FormatUint(mRef.ID, 10),
|
|
},
|
|
},
|
|
)
|
|
|
|
// Records
|
|
rRef := h.makeRecord(mRef)
|
|
record := h.makeRecord(module,
|
|
&types.RecordValue{
|
|
Name: "name",
|
|
Value: "value; name",
|
|
},
|
|
&types.RecordValue{
|
|
Name: "ref",
|
|
Value: strconv.FormatUint(rRef.ID, 10),
|
|
Ref: rRef.ID,
|
|
},
|
|
)
|
|
|
|
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "update")
|
|
|
|
h.apiInit().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)).
|
|
JSON(fmt.Sprintf(`{"values": [{"name": "name", "value": "changed-val"}, {"name": "ref", "value": "%d"}]}`, rRef.ID)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
End()
|
|
|
|
r := h.lookupRecordByID(module, record.ID)
|
|
h.a.Equal(rRef.ID, r.Values.Get("ref", 0).Ref)
|
|
h.a.Equal(strconv.FormatUint(rRef.ID, 10), r.Values.Get("ref", 0).Value)
|
|
h.a.NotNil(r)
|
|
}
|
|
|
|
func TestRecordUpdate_refChanged(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
namespace := h.makeNamespace("record testing namespace")
|
|
|
|
// mods
|
|
mRef := h.makeRecordModuleWithFieldsOnNs("record testing module (ref)", namespace)
|
|
module := h.makeRecordModuleWithFieldsOnNs("record testing module", namespace,
|
|
&types.ModuleField{
|
|
Name: "name",
|
|
Kind: "String",
|
|
},
|
|
&types.ModuleField{
|
|
Name: "ref",
|
|
Kind: "Record",
|
|
Options: types.ModuleFieldOptions{
|
|
"moduleID": strconv.FormatUint(mRef.ID, 10),
|
|
},
|
|
},
|
|
)
|
|
|
|
// Records
|
|
rRef := h.makeRecord(mRef)
|
|
rRef2 := h.makeRecord(mRef)
|
|
record := h.makeRecord(module,
|
|
&types.RecordValue{
|
|
Name: "name",
|
|
Value: "value; name",
|
|
},
|
|
&types.RecordValue{
|
|
Name: "ref",
|
|
Value: strconv.FormatUint(rRef.ID, 10),
|
|
Ref: rRef.ID,
|
|
},
|
|
)
|
|
|
|
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "update")
|
|
|
|
h.apiInit().
|
|
Debug().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)).
|
|
JSON(fmt.Sprintf(`{"values": [{"name": "name", "value": "changed-val"}, {"name": "ref", "value": "%d"}]}`, rRef2.ID)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
End()
|
|
|
|
r := h.lookupRecordByID(module, record.ID)
|
|
h.a.Equal(rRef2.ID, r.Values.Get("ref", 0).Ref)
|
|
h.a.Equal(strconv.FormatUint(rRef2.ID, 10), r.Values.Get("ref", 0).Value)
|
|
h.a.NotNil(r)
|
|
}
|
|
|
|
func TestRecordUpdate_deleteOld(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module")
|
|
record := h.makeRecord(module, &types.RecordValue{Name: "name", Value: "test name"}, &types.RecordValue{Name: "email", Value: "test@email.tld"})
|
|
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "update")
|
|
|
|
h.apiInit().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)).
|
|
JSON(fmt.Sprintf(`{"values": [{"name": "email", "value": "test@email.tld"}]}`)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
Assert(jsonpath.Len(`$.response.values`, 1)).
|
|
End()
|
|
|
|
r := h.lookupRecordByID(module, record.ID)
|
|
h.a.NotNil(r)
|
|
h.a.Empty(r.Values.FilterByName("name"))
|
|
}
|
|
|
|
func TestRecordDeleteForbidden(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module")
|
|
record := h.makeRecord(module)
|
|
|
|
h.apiInit().
|
|
Delete(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertError("record.errors.notAllowedToDelete")).
|
|
End()
|
|
}
|
|
|
|
func TestRecordDelete(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module")
|
|
record := h.makeRecord(module)
|
|
|
|
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "delete")
|
|
|
|
h.apiInit().
|
|
Delete(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
End()
|
|
|
|
r := h.lookupRecordByID(module, record.ID)
|
|
h.a.NotNil(r.DeletedAt)
|
|
}
|
|
|
|
func TestRecordAttachment(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
namespace := h.makeNamespace("record attachment testing namespace")
|
|
|
|
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
|
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "read", "record.create")
|
|
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "read")
|
|
helpers.AllowMe(h, types.ModuleFieldRbacResource(0, 0, 0), "record.value.read", "record.value.update")
|
|
|
|
const maxSizeLimit = 1
|
|
|
|
module := h.makeModule(
|
|
namespace,
|
|
"module",
|
|
&types.ModuleField{Name: "no_constraints", Kind: "File"},
|
|
&types.ModuleField{
|
|
Name: "max_size",
|
|
Kind: "File",
|
|
Options: types.ModuleFieldOptions{"maxSize": maxSizeLimit},
|
|
},
|
|
&types.ModuleField{
|
|
Name: "img_only",
|
|
Kind: "File",
|
|
Options: types.ModuleFieldOptions{
|
|
"maxSize": maxSizeLimit,
|
|
"mimetypes": "image/gif, image/png, image/jpeg",
|
|
},
|
|
},
|
|
&types.ModuleField{Name: "str", Kind: "String"},
|
|
&types.ModuleField{
|
|
Name: "csv_only",
|
|
Kind: "File",
|
|
Options: types.ModuleFieldOptions{
|
|
"mimetypes": "text/csv",
|
|
},
|
|
},
|
|
)
|
|
|
|
xxlBlob := bytes.Repeat([]byte("0"), maxSizeLimit*1_000_000+1)
|
|
|
|
testImgFh, err := os.ReadFile("./testdata/test.png")
|
|
h.noError(err)
|
|
|
|
testCsvFh, err := os.ReadFile("./testdata/test.csv")
|
|
h.noError(err)
|
|
|
|
defer func() {
|
|
// reset settings after we're done
|
|
systemService.CurrentSettings.Compose.Record.Attachments.MaxSize = 0
|
|
systemService.CurrentSettings.Compose.Record.Attachments.Mimetypes = nil
|
|
}()
|
|
|
|
systemService.CurrentSettings.Compose.Record.Attachments.MaxSize = maxSizeLimit
|
|
systemService.CurrentSettings.Compose.Record.Attachments.Mimetypes = []string{}
|
|
|
|
cc := []struct {
|
|
name string
|
|
file []byte
|
|
fname string
|
|
mtype string
|
|
form map[string]string
|
|
test func(*http.Response, *http.Request) error
|
|
}{
|
|
{
|
|
"empty file",
|
|
[]byte(""),
|
|
"empty",
|
|
"plain/text",
|
|
map[string]string{"fieldName": "no_constraints"},
|
|
helpers.AssertError("attachment.errors.notAllowedToCreateEmptyAttachment"),
|
|
},
|
|
{
|
|
"no file",
|
|
nil,
|
|
"empty",
|
|
"plain/text",
|
|
map[string]string{"fieldName": "no_constraints"},
|
|
helpers.AssertError("attachment.errors.notAllowedToCreateEmptyAttachment"),
|
|
},
|
|
{
|
|
"no field",
|
|
[]byte("."),
|
|
"dot",
|
|
"plain/text",
|
|
nil,
|
|
helpers.AssertError("attachment.errors.invalidModuleField"),
|
|
},
|
|
{
|
|
"invalid field",
|
|
[]byte("."),
|
|
"dot",
|
|
"plain/text",
|
|
map[string]string{"fieldName": "str"},
|
|
helpers.AssertError("attachment.errors.invalidModuleField"),
|
|
},
|
|
{
|
|
"valid upload, no constraints",
|
|
[]byte("."),
|
|
"dot",
|
|
"plain/text",
|
|
map[string]string{"fieldName": "no_constraints"},
|
|
helpers.AssertNoErrors,
|
|
},
|
|
{
|
|
"global max size - over sized",
|
|
xxlBlob,
|
|
"numbers",
|
|
"plain/text",
|
|
map[string]string{"fieldName": "no_constraints"},
|
|
helpers.AssertError("attachment.errors.tooLarge"),
|
|
},
|
|
{
|
|
"field max size - ok",
|
|
[]byte("12345"),
|
|
"numbers",
|
|
"plain/text",
|
|
map[string]string{"fieldName": "max_size"},
|
|
helpers.AssertNoErrors,
|
|
},
|
|
{
|
|
"field max size - over sized",
|
|
xxlBlob,
|
|
"numbers",
|
|
"plain/text",
|
|
map[string]string{"fieldName": "max_size"},
|
|
helpers.AssertError("attachment.errors.tooLarge"),
|
|
},
|
|
{
|
|
"global mimetype - invalid",
|
|
testImgFh,
|
|
"numbers.gif",
|
|
"image/gif",
|
|
map[string]string{"fieldName": "no_constraints"},
|
|
helpers.AssertError("attachment.errors.failedToProcessImage"),
|
|
},
|
|
{
|
|
"field mimetype - ok",
|
|
testImgFh,
|
|
"image.png",
|
|
"image/gif",
|
|
map[string]string{"fieldName": "img_only"},
|
|
helpers.AssertNoErrors,
|
|
},
|
|
{
|
|
"field mimetype - invalid",
|
|
testImgFh,
|
|
"image.png",
|
|
"image/gif",
|
|
map[string]string{"fieldName": "img_only"},
|
|
helpers.AssertNoErrors,
|
|
},
|
|
{
|
|
"csv file - ok",
|
|
testCsvFh,
|
|
"testCSV",
|
|
"text/csv",
|
|
map[string]string{"fieldName": "csv_only"},
|
|
helpers.AssertNoErrors,
|
|
},
|
|
}
|
|
|
|
for _, c := range cc {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
h.t = t
|
|
|
|
helpers.InitFileUpload(t, h.apiInit(),
|
|
fmt.Sprintf("/namespace/%d/module/%d/record/attachment", module.NamespaceID, module.ID),
|
|
c.form,
|
|
c.file,
|
|
c.fname,
|
|
c.mtype,
|
|
).
|
|
Status(http.StatusOK).
|
|
Assert(c.test).
|
|
End()
|
|
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRecordExport(t *testing.T) {
|
|
t.Skip("@todo not yet refactored")
|
|
|
|
// h := newHelper(t)
|
|
// h.clearRecords()
|
|
|
|
// module := h.repoMakeRecordModuleWithFields("record export module")
|
|
// expected := "id,name\n"
|
|
// for i := 0; i < 10; i++ {
|
|
// r := h.makeRecord(module, &types.RecordValue{Name: "name", Value: fmt.Sprintf("d%d", i), Place: uint(i)})
|
|
// expected += fmt.Sprintf("%d,d%d\n", r.ID, i)
|
|
// }
|
|
|
|
// // we'll not use standard asserts (AssertNoErrors) here,
|
|
// // because we're not returning JSON errors.
|
|
// r := h.apiInit().
|
|
// Get(fmt.Sprintf("/namespace/%d/module/%d/record/export.csv", module.NamespaceID, module.ID)).
|
|
// Query("fields", "name").
|
|
// Header("Accept", "application/json").
|
|
// Expect(t).
|
|
// Status(http.StatusOK).
|
|
// End()
|
|
|
|
// b, err := ioutil.ReadAll(r.Response.Body)
|
|
// h.noError(err)
|
|
// h.a.Equal(expected, string(b))
|
|
}
|
|
|
|
func (h helper) apiInitRecordImport(api *apitest.APITest, url, f string, file []byte) *apitest.Response {
|
|
body := &bytes.Buffer{}
|
|
writer := multipart.NewWriter(body)
|
|
part, err := writer.CreateFormFile("upload", f)
|
|
h.noError(err)
|
|
|
|
_, err = part.Write(file)
|
|
h.noError(err)
|
|
h.noError(writer.Close())
|
|
|
|
return api.
|
|
Post(url).
|
|
Header("Accept", "application/json").
|
|
Body(body.String()).
|
|
ContentType(writer.FormDataContentType()).
|
|
Expect(h.t).
|
|
Status(http.StatusOK)
|
|
}
|
|
|
|
func (h helper) apiRunRecordImport(api *apitest.APITest, url, b string) *apitest.Response {
|
|
return api.
|
|
Patch(url).
|
|
Header("Accept", "application/json").
|
|
JSON(b).
|
|
Expect(h.t).
|
|
Status(http.StatusOK)
|
|
}
|
|
|
|
func TestRecordImportInit(t *testing.T) {
|
|
t.Skip("@todo not yet refactored")
|
|
|
|
// h := newHelper(t)
|
|
// h.clearRecords()
|
|
|
|
// module := h.repoMakeRecordModuleWithFields("record import init module")
|
|
// tests := []struct {
|
|
// Name string
|
|
// Content string
|
|
// }{
|
|
// {
|
|
// Name: "f1.csv",
|
|
// Content: "name,email\nv1,v2\n",
|
|
// },
|
|
// {
|
|
// Name: "f1.json",
|
|
// Content: `{"name":"v1","email":"v2"}` + "\n",
|
|
// },
|
|
// }
|
|
|
|
// for _, test := range tests {
|
|
// t.Run(test.Name, func(t *testing.T) {
|
|
// url := fmt.Sprintf("/namespace/%d/module/%d/record/import", module.NamespaceID, module.ID)
|
|
// h.apiInitRecordImport(h.apiInit(), url, test.Name, []byte(test.Content)).
|
|
// Assert(jsonpath.Present("$.response.sessionID")).
|
|
// Assert(jsonpath.Present(`$.response.fields.name==""`)).
|
|
// Assert(jsonpath.Present(`$.response.fields.email==""`)).
|
|
// Assert(jsonpath.Present("$.response.progress")).
|
|
// Assert(jsonpath.Present("$.response.progress.entryCount==1")).
|
|
// End()
|
|
// })
|
|
// }
|
|
}
|
|
|
|
func TestRecordImportInit_invalidFileFormat(t *testing.T) {
|
|
t.Skip("@todo not yet refactored")
|
|
|
|
// h := newHelper(t)
|
|
// h.clearRecords()
|
|
|
|
// module := h.repoMakeRecordModuleWithFields("record import init module")
|
|
// url := fmt.Sprintf("/namespace/%d/module/%d/record/import", module.NamespaceID, module.ID)
|
|
// h.apiInitRecordImport(h.apiInit(), url, "invalid", []byte("nope")).
|
|
// Assert(helpers.AssertError("compose.service.RecordImportFormatNotSupported")).
|
|
// End()
|
|
// }
|
|
|
|
// func TestRecordImportRun(t *testing.T) {
|
|
// h := newHelper(t)
|
|
// h.clearRecords()
|
|
// helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "record.create")
|
|
|
|
// module := h.repoMakeRecordModuleWithFields("record import run module")
|
|
// tests := []struct {
|
|
// Name string
|
|
// Content string
|
|
// }{
|
|
// {
|
|
// Name: "f1.csv",
|
|
// Content: "fname,femail\nv1,v2\n",
|
|
// },
|
|
// }
|
|
|
|
// for _, test := range tests {
|
|
// t.Run(test.Name, func(t *testing.T) {
|
|
// url := fmt.Sprintf("/namespace/%d/module/%d/record/import", module.NamespaceID, module.ID)
|
|
// rsp := &rImportSession{}
|
|
// api := h.apiInit()
|
|
|
|
// r := h.apiInitRecordImport(api, url, test.Name, []byte(test.Content)).End()
|
|
// r.JSON(rsp)
|
|
|
|
// h.apiRunRecordImport(api, fmt.Sprintf("%s/%s", url, rsp.Response.SessionID), `{"fields":{"fname":"name","femail":"email"},"onError":"fail"}`).
|
|
// Assert(helpers.AssertNoErrors).
|
|
// Assert(jsonpath.Present("$.response.progress")).
|
|
// Assert(jsonpath.Present(`$.response.fields.fname=="name"`)).
|
|
// Assert(jsonpath.Present(`$.response.fields.femail=="email"`)).
|
|
// End()
|
|
// })
|
|
// }
|
|
}
|
|
|
|
func TestRecordImportRun_sessionNotFound(t *testing.T) {
|
|
t.Skip("@todo not yet refactored")
|
|
|
|
// h := newHelper(t)
|
|
// h.clearRecords()
|
|
|
|
// module := h.repoMakeRecordModuleWithFields("record import run module")
|
|
// h.apiRunRecordImport(h.apiInit(), fmt.Sprintf("/namespace/%d/module/%d/record/import/123", module.NamespaceID, module.ID), `{"fields":{"fname":"name","femail":"email"},"onError":"fail"}`).
|
|
// Assert(helpers.AssertError("compose.service.RecordImportSessionNotFound")).
|
|
// End()
|
|
}
|
|
|
|
// @todo revert whe we add import RBAC operations
|
|
// func TestRecordImportRunForbidden(t *testing.T) {
|
|
// h := newHelper(t)
|
|
// h.clearRecords()
|
|
// helpers.DenyMe(h, types.ModuleRbacResource(0, 0), "record.create")
|
|
|
|
// module := h.repoMakeRecordModuleWithFields("record import run module")
|
|
// tests := []struct {
|
|
// Name string
|
|
// Content string
|
|
// }{
|
|
// {
|
|
// Name: "f1.csv",
|
|
// Content: "fname,femail\nv1,v2\n",
|
|
// },
|
|
// }
|
|
|
|
// for _, test := range tests {
|
|
// t.Run(test.Name, func(t *testing.T) {
|
|
// url := fmt.Sprintf("/namespace/%d/module/%d/record/import", module.NamespaceID, module.ID)
|
|
// rsp := &rImportSession{}
|
|
// api := h.apiInit()
|
|
|
|
// r := h.apiInitRecordImport(api, url, test.Name, []byte(test.Content)).End()
|
|
// r.JSON(rsp)
|
|
|
|
// h.apiRunRecordImport(api, fmt.Sprintf("%s/%s", url, rsp.Response.SessionID), `{"fields":{"fname":"name","femail":"email"},"onError":"fail"}`).
|
|
// Assert(helpers.AssertErrorP("record.errors.notAllowedToCreate for module")).
|
|
// End()
|
|
// })
|
|
// }
|
|
// }
|
|
|
|
// @todo revert whe we add import RBAC operations
|
|
// func TestRecordImportRunForbidden_field(t *testing.T) {
|
|
// h := newHelper(t)
|
|
// h.clearRecords()
|
|
// helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "record.create")
|
|
// helpers.AllowMe(h, types.ModuleFieldRbacResource(0, 0, 0), "record.value.update")
|
|
|
|
// module := h.repoMakeRecordModuleWithFields("record import run module")
|
|
|
|
// f := module.Fields.FindByName("name")
|
|
// helpers.DenyMe(h, f.RbacResource(), "record.value.update")
|
|
|
|
// tests := []struct {
|
|
// Name string
|
|
// Content string
|
|
// }{
|
|
// {
|
|
// Name: "f1.csv",
|
|
// Content: "fname,femail\nv1,v2\n",
|
|
// },
|
|
// }
|
|
|
|
// for _, test := range tests {
|
|
// t.Run(test.Name, func(t *testing.T) {
|
|
// h.t = t
|
|
// url := fmt.Sprintf("/namespace/%d/module/%d/record/import", module.NamespaceID, module.ID)
|
|
// rsp := &rImportSession{}
|
|
// api := h.apiInit()
|
|
|
|
// r := h.apiInitRecordImport(api, url, test.Name, []byte(test.Content)).End()
|
|
// r.JSON(rsp)
|
|
|
|
// h.apiRunRecordImport(api, fmt.Sprintf("%s/%s", url, rsp.Response.SessionID), `{"fields":{"fname":"name","femail":"email"},"onError":"fail"}`).
|
|
// Assert(helpers.AssertErrorP("1 issue(s) found")).
|
|
// End()
|
|
// })
|
|
// }
|
|
// }
|
|
|
|
func TestRecordImportRunFieldError_missing(t *testing.T) {
|
|
t.Skip("@todo not yet refactored")
|
|
|
|
// h := newHelper(t)
|
|
// h.clearRecords()
|
|
// helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "record.create")
|
|
|
|
// module := h.repoMakeRecordModuleWithFieldsRequired("record import run module")
|
|
|
|
// tests := []struct {
|
|
// Name string
|
|
// Content string
|
|
// }{
|
|
// {
|
|
// Name: "f1.csv",
|
|
// Content: "fname,femail\n,v2\n",
|
|
// },
|
|
// }
|
|
|
|
// for _, test := range tests {
|
|
// t.Run(test.Name, func(t *testing.T) {
|
|
// url := fmt.Sprintf("/namespace/%d/module/%d/record/import", module.NamespaceID, module.ID)
|
|
// rsp := &rImportSession{}
|
|
// api := h.apiInit()
|
|
|
|
// r := h.apiInitRecordImport(api, url, test.Name, []byte(test.Content)).End()
|
|
// r.JSON(rsp)
|
|
|
|
// h.apiRunRecordImport(api, fmt.Sprintf("%s/%s", url, rsp.Response.SessionID), `{"fields":{"femail":"email"},"onError":"skip"}`).
|
|
// End()
|
|
|
|
// api.Get(fmt.Sprintf("%s/%s", url, rsp.Response.SessionID)).
|
|
// Expect(h.t).
|
|
// Status(http.StatusOK).
|
|
// Assert(helpers.AssertNoErrors).
|
|
// Assert(jsonpath.Present("$.response.progress.failLog.errors[\"empty field name\"]")).
|
|
// End()
|
|
// })
|
|
// }
|
|
}
|
|
|
|
func TestRecordImportImportProgress(t *testing.T) {
|
|
t.Skip("@todo not yet refactored")
|
|
|
|
// h := newHelper(t)
|
|
// h.clearRecords()
|
|
|
|
// module := h.repoMakeRecordModuleWithFields("record import session module")
|
|
// tests := []struct {
|
|
// Name string
|
|
// Content string
|
|
// }{
|
|
// {
|
|
// Name: "f1.csv",
|
|
// Content: "fname,femail\nv1,v2\n",
|
|
// },
|
|
// }
|
|
|
|
// for _, test := range tests {
|
|
// t.Run(test.Name, func(t *testing.T) {
|
|
// url := fmt.Sprintf("/namespace/%d/module/%d/record/import", module.NamespaceID, module.ID)
|
|
// rsp := &rImportSession{}
|
|
// api := h.apiInit()
|
|
|
|
// r := h.apiInitRecordImport(api, url, test.Name, []byte(test.Content)).End()
|
|
// r.JSON(rsp)
|
|
|
|
// api.Get(fmt.Sprintf("%s/%s", url, rsp.Response.SessionID)).
|
|
// Expect(h.t).
|
|
// Status(http.StatusOK).
|
|
// Assert(helpers.AssertNoErrors).
|
|
// Assert(jsonpath.Present("$.response.progress")).
|
|
// End()
|
|
// })
|
|
// }
|
|
}
|
|
|
|
func TestRecordImportImportProgress_sessionNotFound(t *testing.T) {
|
|
t.Skip("@todo not yet refactored")
|
|
|
|
// h := newHelper(t)
|
|
// h.clearRecords()
|
|
|
|
// module := h.repoMakeRecordModuleWithFields("record import module")
|
|
// h.apiInit().
|
|
// Get(fmt.Sprintf("/namespace/%d/module/%d/record/import/123", module.NamespaceID, module.ID)).
|
|
// Header("Accept", "application/json").
|
|
// Expect(h.t).
|
|
// Status(http.StatusOK).
|
|
// Assert(helpers.AssertError("compose.service.RecordImportSessionNotFound")).
|
|
// End()
|
|
}
|
|
|
|
func TestRecordFieldModulePermissionCheck(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
// make a standard module, and prevent (DENY) current user to
|
|
// read from "name" and update "email" fields
|
|
module := h.repoMakeRecordModuleWithFields("record testing module")
|
|
helpers.AllowMe(h, module.RbacResource(), "records.create", "records.search")
|
|
helpers.DenyMe(h, module.Fields.FindByName("name").RbacResource(), "record.value.read")
|
|
helpers.DenyMe(h, module.Fields.FindByName("email").RbacResource(), "record.value.update")
|
|
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "record.create")
|
|
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "update")
|
|
|
|
record := h.makeRecord(
|
|
module,
|
|
&types.RecordValue{Name: "name", Value: "should not be readable"},
|
|
&types.RecordValue{Name: "email", Value: "should not be writable"},
|
|
)
|
|
|
|
// Fetching record should work as before but without read-protected fields
|
|
h.apiInit().
|
|
Get(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
// should not return name
|
|
Assert(jsonpath.NotPresent(`$.response.values[? @.name=="name"]`)).
|
|
// should return email
|
|
Assert(jsonpath.Present(`$.response.values[? @.name=="email"]`)).
|
|
End()
|
|
|
|
// Searching records should work as before but without read-protected fields
|
|
h.apiInit().
|
|
Get(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
// should not return name
|
|
Assert(jsonpath.NotPresent(`$.response.set[0].values[? @.name=="name"]`)).
|
|
// should return email
|
|
Assert(jsonpath.Present(`$.response.set[0].values[? @.name=="email"]`)).
|
|
End()
|
|
|
|
bb := map[string]func() *apitest.Request{
|
|
"update": func() *apitest.Request {
|
|
return h.apiInit().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/%d", module.NamespaceID, module.ID, record.ID))
|
|
},
|
|
|
|
"create": func() *apitest.Request {
|
|
return h.apiInit().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/", module.NamespaceID, module.ID))
|
|
},
|
|
}
|
|
|
|
for name, b := range bb {
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Run("field:email", func(t *testing.T) {
|
|
// Try to change email (not writable!), expect error...
|
|
b().JSON(fmt.Sprintf(`{"values": [{"name": "email", "value": "changed-email"}]}`)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertErrorP("1 issue(s) found")).
|
|
End()
|
|
})
|
|
|
|
t.Run("field:name", func(t *testing.T) {
|
|
// Try to change name, (not readable), expect it to work
|
|
b().JSON(fmt.Sprintf(`{"values": [{"name": "name", "value": "changed-name"}]}`)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
End()
|
|
})
|
|
|
|
t.Run("field:description", func(t *testing.T) {
|
|
// Try to change description, (no perm. rules), expect it to work
|
|
b().JSON(fmt.Sprintf(`{"values": [{"name": "description", "value": "changed-description"}]}`)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
End()
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRecordLabels(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
|
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "read", "record.create", "records.search")
|
|
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "update", "read")
|
|
|
|
var (
|
|
ns = h.makeNamespace("some-namespace")
|
|
mod = h.makeModule(ns, "some-module", &types.ModuleField{Kind: "String", Name: "dummy"})
|
|
ID uint64
|
|
)
|
|
|
|
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "record.create", "read")
|
|
helpers.AllowMe(h, types.ModuleFieldRbacResource(0, 0, 0), "record.value.read")
|
|
helpers.AllowMe(h, types.ModuleFieldRbacResource(0, 0, 0), "record.value.update")
|
|
|
|
t.Run("create", func(t *testing.T) {
|
|
var (
|
|
req = require.New(t)
|
|
|
|
payload = struct {
|
|
Response *types.Record
|
|
}{}
|
|
|
|
rec = &types.Record{
|
|
Values: types.RecordValueSet{&types.RecordValue{Name: "dummy", Value: "dummy"}},
|
|
Meta: map[string]any{
|
|
"foo": "bar",
|
|
"bar": "42",
|
|
},
|
|
}
|
|
)
|
|
|
|
h.apiInit().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/", ns.ID, mod.ID)).
|
|
JSON(helpers.JSON(rec)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
End().
|
|
JSON(&payload)
|
|
|
|
req.NotNil(payload.Response)
|
|
req.NotZero(payload.Response.ID)
|
|
|
|
h.a.Equal(payload.Response.Meta["foo"], "bar",
|
|
"labels must contain foo with value bar")
|
|
h.a.Equal(payload.Response.Meta["bar"], "42",
|
|
"labels must contain bar with value 42")
|
|
|
|
ID = payload.Response.ID
|
|
})
|
|
|
|
t.Run("update", func(t *testing.T) {
|
|
if ID == 0 {
|
|
t.Skip("label/create test not ran")
|
|
}
|
|
|
|
var (
|
|
req = require.New(t)
|
|
|
|
payload = struct {
|
|
Response *types.Record
|
|
}{}
|
|
|
|
rec = &types.Record{
|
|
ID: ID,
|
|
Values: types.RecordValueSet{&types.RecordValue{Name: "dummy", Value: "dummy"}},
|
|
Meta: map[string]any{
|
|
"foo": "baz",
|
|
"baz": "123",
|
|
},
|
|
}
|
|
)
|
|
|
|
h.apiInit().
|
|
Post(fmt.Sprintf("/namespace/%d/module/%d/record/%d", ns.ID, mod.ID, ID)).
|
|
JSON(helpers.JSON(rec)).
|
|
Header("Accept", "application/json").
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
End().
|
|
JSON(&payload)
|
|
req.NotZero(payload.Response.ID)
|
|
|
|
// disabled for now
|
|
//req.Nil(payload.Response.UpdatedAt, "updatedAt must not change after changing labels")
|
|
|
|
req.Equal(payload.Response.Meta["foo"], "baz",
|
|
"meta must contain foo with value baz")
|
|
req.NotContains(payload.Response.Meta, "bar",
|
|
"meta must not contain bar")
|
|
req.Equal(payload.Response.Meta["baz"], "123",
|
|
"meta must contain baz with value 123")
|
|
})
|
|
|
|
t.Run("search", func(t *testing.T) {
|
|
if ID == 0 {
|
|
t.Skip("label/create test not ran")
|
|
}
|
|
|
|
var (
|
|
req = require.New(t)
|
|
|
|
payload = struct {
|
|
Response struct {
|
|
Set types.RecordSet
|
|
}
|
|
}{}
|
|
|
|
get = func(meta string, p any) {
|
|
h.apiInit().
|
|
Getf("/namespace/%d/module/%d/record/", ns.ID, mod.ID).
|
|
Header("Accept", "application/json").
|
|
Query("meta", meta).
|
|
Expect(t).
|
|
Status(http.StatusOK).
|
|
Assert(helpers.AssertNoErrors).
|
|
End().
|
|
JSON(p)
|
|
}
|
|
)
|
|
|
|
get("baz=123", &payload)
|
|
t.Log("is record included in search result?")
|
|
req.NotEmpty(payload.Response.Set)
|
|
req.Len(payload.Response.Set, 1)
|
|
req.NotNil(payload.Response.Set.FindByID(ID))
|
|
|
|
t.Log("is meta included")
|
|
req.NotNil(payload.Response.Set.FindByID(ID).Meta)
|
|
|
|
get("k2342341241241244=bar", &payload)
|
|
t.Log("no records with this meta constraints should exist")
|
|
req.Empty(payload.Response.Set)
|
|
|
|
get("baz", &payload)
|
|
t.Log("one record should be found")
|
|
req.Len(payload.Response.Set, 1)
|
|
req.NotNil(payload.Response.Set.FindByID(ID))
|
|
})
|
|
}
|
|
|
|
func TestRecordReports(t *testing.T) {
|
|
t.Skip("@todo not yet refactored")
|
|
|
|
// h := newHelper(t)
|
|
// h.clearRecords()
|
|
|
|
// helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "records.search")
|
|
// helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
|
// helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "read", "record.create")
|
|
// helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "read")
|
|
|
|
// var (
|
|
// ns = h.makeNamespace("some-namespace")
|
|
// mod = h.makeModule(ns, "some-module",
|
|
// &types.ModuleField{
|
|
// Kind: "Number",
|
|
// Name: "n_float",
|
|
// Options: types.ModuleFieldOptions{"precision": 2},
|
|
// },
|
|
// &types.ModuleField{
|
|
// Kind: "Number",
|
|
// Name: "n_int",
|
|
// Options: types.ModuleFieldOptions{"precision": 0},
|
|
// },
|
|
// &types.ModuleField{
|
|
// Kind: "Number",
|
|
// Name: "n_int_multi",
|
|
// Multi: true,
|
|
// Options: types.ModuleFieldOptions{"precision": 0},
|
|
// },
|
|
// )
|
|
// )
|
|
|
|
// h.makeRecord(mod,
|
|
// &types.RecordValue{Name: "n_float", Value: "1.1"},
|
|
// &types.RecordValue{Name: "n_int", Value: "1"},
|
|
// &types.RecordValue{Name: "n_int_multi", Value: "1"},
|
|
// )
|
|
|
|
// h.makeRecord(mod,
|
|
// &types.RecordValue{Name: "n_float", Value: "2.3"},
|
|
// &types.RecordValue{Name: "n_int", Value: "2"},
|
|
// &types.RecordValue{Name: "n_int_multi", Value: "1"},
|
|
// &types.RecordValue{Name: "n_int_multi", Value: "2", Place: 1},
|
|
// &types.RecordValue{Name: "n_int_multi", Value: "3", Place: 2},
|
|
// )
|
|
|
|
// t.Run("base metrics", func(t *testing.T) {
|
|
// tcc := []struct {
|
|
// op string
|
|
// expCount float64
|
|
// expFloat float64
|
|
// expInteger float64
|
|
// expMultInt float64
|
|
// }{
|
|
// {
|
|
// op: "COUNT",
|
|
// expCount: 2,
|
|
// expFloat: 2,
|
|
// expInteger: 2,
|
|
// expMultInt: 4, // counting multi values as well
|
|
// },
|
|
// {
|
|
// op: "SUM",
|
|
// expCount: 2,
|
|
// expFloat: 3.4,
|
|
// expInteger: 3,
|
|
// expMultInt: 7, // summing multi values as well
|
|
// },
|
|
// {
|
|
// op: "MAX",
|
|
// expCount: 2,
|
|
// expFloat: 2.3,
|
|
// expInteger: 2,
|
|
// expMultInt: 3, // all values, even the last one
|
|
// },
|
|
// {
|
|
// op: "MIN",
|
|
// expCount: 2,
|
|
// expFloat: 1.1,
|
|
// expInteger: 1,
|
|
// expMultInt: 1,
|
|
// },
|
|
// {
|
|
// op: "AVG",
|
|
// expCount: 2,
|
|
// expFloat: 1.7,
|
|
// expInteger: 1.5,
|
|
// expMultInt: 1.75, // all values!
|
|
// },
|
|
// // @todo
|
|
// // {
|
|
// // op: "STD",
|
|
// // expFloat: 0,
|
|
// // expInteger: 0,
|
|
// // },
|
|
// }
|
|
|
|
// for _, tc := range tcc {
|
|
// t.Run("basic operations; float; "+tc.op, func(t *testing.T) {
|
|
// h.apiInit().
|
|
// Get(fmt.Sprintf("/namespace/%d/module/%d/record/report", mod.NamespaceID, mod.ID)).
|
|
// Query("metrics", tc.op+"(n_float) as rp").
|
|
// Query("dimensions", "DATE_FORMAT(created_at,'Y-01-01')").
|
|
// Header("Accept", "application/json").
|
|
// Expect(t).
|
|
// Status(http.StatusOK).
|
|
// Assert(jsonpath.Len(`$.response`, 1)).
|
|
// Assert(jsonpath.Equal(`$.response[0].count`, tc.expCount)).
|
|
// Assert(jsonpath.Equal(`$.response[0].rp`, tc.expFloat)).
|
|
// End()
|
|
// })
|
|
// t.Run("basic operations; int; "+tc.op, func(t *testing.T) {
|
|
// h.apiInit().
|
|
// Get(fmt.Sprintf("/namespace/%d/module/%d/record/report", mod.NamespaceID, mod.ID)).
|
|
// Query("metrics", tc.op+"(n_int) as rp").
|
|
// Query("dimensions", "DATE_FORMAT(created_at,'Y-01-01')").
|
|
// Header("Accept", "application/json").
|
|
// Expect(t).
|
|
// Status(http.StatusOK).
|
|
// Assert(jsonpath.Len(`$.response`, 1)).
|
|
// Assert(jsonpath.Equal(`$.response[0].count`, tc.expCount)).
|
|
// Assert(jsonpath.Equal(`$.response[0].rp`, tc.expInteger)).
|
|
// End()
|
|
// })
|
|
// t.Run("basic operations; int multi-value-field; "+tc.op, func(t *testing.T) {
|
|
// h.apiInit().
|
|
// Get(fmt.Sprintf("/namespace/%d/module/%d/record/report", mod.NamespaceID, mod.ID)).
|
|
// Query("metrics", tc.op+"(n_int_multi) as rp").
|
|
// Query("dimensions", "DATE_FORMAT(created_at,'Y-01-01')").
|
|
// Header("Accept", "application/json").
|
|
// Expect(t).
|
|
// Status(http.StatusOK).
|
|
// Assert(jsonpath.Len(`$.response`, 1)).
|
|
// Assert(jsonpath.Equal(`$.response[0].count`, tc.expCount)).
|
|
// Assert(jsonpath.Equal(`$.response[0].rp`, tc.expMultInt)).
|
|
// End()
|
|
// })
|
|
// }
|
|
// })
|
|
}
|
|
|
|
func TestRecordClone(t *testing.T) {
|
|
h := newHelper(t)
|
|
h.clearRecords()
|
|
|
|
module := h.repoMakeRecordModuleWithFields("record testing module",
|
|
&types.ModuleField{Name: "name", Kind: "String"},
|
|
&types.ModuleField{Name: "description", Kind: "String"},
|
|
)
|
|
|
|
// Create original record
|
|
original := h.makeRecord(module,
|
|
&types.RecordValue{Name: "name", Value: "Original Name"},
|
|
&types.RecordValue{Name: "description", Value: "Original Description"},
|
|
)
|
|
|
|
// Clone the record
|
|
cloned := original.Clone()
|
|
|
|
// Verify cloned record
|
|
h.a.NotNil(cloned)
|
|
h.a.Equal(original.ID, cloned.ID)
|
|
h.a.Equal(original.ModuleID, cloned.ModuleID)
|
|
h.a.Equal(original.NamespaceID, cloned.NamespaceID)
|
|
h.a.Equal(original.Values.Get("name", 0).Value, cloned.Values.Get("name", 0).Value)
|
|
h.a.Equal(original.Values.Get("description", 0).Value, cloned.Values.Get("description", 0).Value)
|
|
h.a.Equal(original.CreatedBy, cloned.CreatedBy)
|
|
h.a.Equal(original.UpdatedBy, cloned.UpdatedBy)
|
|
h.a.Equal(original.DeletedBy, cloned.DeletedBy)
|
|
h.a.Equal(original.CreatedAt, cloned.CreatedAt)
|
|
h.a.Equal(original.UpdatedAt, cloned.UpdatedAt)
|
|
h.a.Equal(original.DeletedAt, cloned.DeletedAt)
|
|
|
|
// Modify cloned record values
|
|
cloned.Values = cloned.Values.Set(&types.RecordValue{Name: "name", Value: "Modified Name"})
|
|
cloned.Values = cloned.Values.Set(&types.RecordValue{Name: "description", Value: "Modified Description"})
|
|
|
|
// Verify original record is not affected
|
|
h.a.Equal("Original Name", original.Values.Get("name", 0).Value, "Original record name should not be affected")
|
|
h.a.Equal("Original Description", original.Values.Get("description", 0).Value, "Original record description should not be affected")
|
|
|
|
// Verify cloned record has new values
|
|
h.a.Equal("Modified Name", cloned.Values.Get("name", 0).Value, "Cloned record should have modified name")
|
|
h.a.Equal("Modified Description", cloned.Values.Get("description", 0).Value, "Cloned record should have modified description")
|
|
}
|