snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
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
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
This commit is contained in:
81
server/tests/compose/attachment_test.go
Normal file
81
server/tests/compose/attachment_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
jsonpath "github.com/steinfletcher/apitest-jsonpath"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (h helper) clearAttachment() {
|
||||
h.clearNamespaces()
|
||||
h.noError(store.TruncateAttachments(context.Background(), service.DefaultStore))
|
||||
}
|
||||
|
||||
func (h helper) repoMakeAttachment(ss ...string) *types.Attachment {
|
||||
var res = &types.Attachment{
|
||||
ID: id.Next(),
|
||||
CreatedAt: time.Now(),
|
||||
Kind: types.RecordAttachment,
|
||||
}
|
||||
|
||||
if len(ss) > 0 {
|
||||
res.Name = ss[0]
|
||||
} else {
|
||||
res.Name = "n_" + rs()
|
||||
}
|
||||
|
||||
h.a.NoError(store.CreateComposeAttachment(context.Background(), service.DefaultStore, res))
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (h helper) lookupAttachmentByID(ID uint64) *types.Attachment {
|
||||
res, err := store.LookupComposeAttachmentByID(context.Background(), service.DefaultStore, ID)
|
||||
h.noError(err)
|
||||
return res
|
||||
}
|
||||
|
||||
func TestAttachmentRead(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearAttachment()
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
a := h.repoMakeAttachment()
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d/attachment/%s/%d", ns.ID, a.Kind, a.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Equal(`$.response.name`, a.Name)).
|
||||
Assert(jsonpath.Equal(`$.response.attachmentID`, fmt.Sprintf("%d", a.ID))).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestAttachmentDelete(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearAttachment()
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
a := h.repoMakeAttachment()
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/namespace/%d/attachment/%s/%d", ns.ID, a.Kind, a.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
a = h.lookupAttachmentByID(a.ID)
|
||||
h.a.NotNil(a)
|
||||
h.a.NotNil(a.DeletedAt)
|
||||
}
|
||||
318
server/tests/compose/chart_test.go
Normal file
318
server/tests/compose/chart_test.go
Normal file
@@ -0,0 +1,318 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
labelTypes "github.com/cortezaproject/corteza/server/pkg/label/types"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
jsonpath "github.com/steinfletcher/apitest-jsonpath"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func (h helper) clearCharts() {
|
||||
h.clearNamespaces()
|
||||
h.noError(store.TruncateComposeCharts(context.Background(), service.DefaultStore))
|
||||
}
|
||||
|
||||
func (h helper) makeChart(ns *types.Namespace, name string) *types.Chart {
|
||||
res := &types.Chart{
|
||||
ID: id.Next(),
|
||||
CreatedAt: time.Now(),
|
||||
Name: name,
|
||||
Handle: name,
|
||||
NamespaceID: ns.ID,
|
||||
}
|
||||
|
||||
h.noError(store.CreateComposeChart(context.Background(), service.DefaultStore, res))
|
||||
return res
|
||||
}
|
||||
|
||||
func (h helper) lookupChartByID(ID uint64) *types.Chart {
|
||||
res, err := store.LookupComposeChartByID(context.Background(), service.DefaultStore, ID)
|
||||
h.noError(err)
|
||||
return res
|
||||
}
|
||||
|
||||
func TestChartRead(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearCharts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "charts.search")
|
||||
helpers.AllowMe(h, types.ChartRbacResource(0, 0), "read")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
m := h.makeChart(ns, "some-chart")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d/chart/%d", ns.ID, m.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Equal(`$.response.name`, m.Name)).
|
||||
Assert(jsonpath.Equal(`$.response.chartID`, fmt.Sprintf("%d", m.ID))).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestChartReadByHandle(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearCharts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "charts.search")
|
||||
helpers.AllowMe(h, types.ChartRbacResource(0, 0), "read")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
c := h.makeChart(ns, "some-chart")
|
||||
|
||||
cbh, err := service.DefaultChart.FindByHandle(h.secCtx(), ns.ID, c.Handle)
|
||||
|
||||
h.noError(err)
|
||||
h.a.NotNil(cbh)
|
||||
h.a.Equal(cbh.ID, c.ID)
|
||||
h.a.Equal(cbh.Handle, c.Handle)
|
||||
}
|
||||
|
||||
func TestChartList(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearCharts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "charts.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
h.makeChart(ns, "chart1")
|
||||
h.makeChart(ns, "chart2")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d/chart/", ns.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestChartList_filterForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearCharts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "charts.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
h.makeChart(ns, "chart")
|
||||
f := h.makeChart(ns, "chart_forbidden")
|
||||
|
||||
helpers.DenyMe(h, f.RbacResource(), "read")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d/chart/", ns.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.NotPresent(`$.response.set[? @.name=="chart_forbidden"]`)).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestChartCreateForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearCharts()
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/chart/", ns.ID)).
|
||||
Header("Accept", "application/json").
|
||||
FormData("name", "some-chart").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("chart.errors.notAllowedToCreate")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestChartCreate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearCharts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "charts.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "chart.create")
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/chart/", ns.ID)).
|
||||
FormData("name", "some-chart").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestChartUpdateForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearCharts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "charts.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
m := h.makeChart(ns, "some-chart")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/chart/%d", ns.ID, m.ID)).
|
||||
Header("Accept", "application/json").
|
||||
FormData("name", "changed-name").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("chart.errors.notAllowedToUpdate")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestChartUpdate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearCharts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "charts.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
res := h.makeChart(ns, "some-chart")
|
||||
helpers.AllowMe(h, types.ChartRbacResource(0, 0), "update")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/chart/%d", ns.ID, res.ID)).
|
||||
FormData("name", "changed-name").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
res = h.lookupChartByID(res.ID)
|
||||
h.a.NotNil(res)
|
||||
h.a.Equal(res.Name, "changed-name")
|
||||
}
|
||||
|
||||
func TestChartDeleteForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearCharts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "charts.search")
|
||||
helpers.AllowMe(h, types.ChartRbacResource(0, 0), "read")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
m := h.makeChart(ns, "some-chart")
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/namespace/%d/chart/%d", ns.ID, m.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("chart.errors.notAllowedToDelete")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestChartDelete(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearCharts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "charts.search")
|
||||
helpers.AllowMe(h, types.ChartRbacResource(0, 0), "read")
|
||||
helpers.AllowMe(h, types.ChartRbacResource(0, 0), "delete")
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
res := h.makeChart(ns, "some-chart")
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/namespace/%d/chart/%d", ns.ID, res.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
res = h.lookupChartByID(res.ID)
|
||||
h.a.NotNil(res.DeletedAt)
|
||||
}
|
||||
|
||||
func TestChartLabels(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearCharts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "charts.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "chart.create")
|
||||
helpers.AllowMe(h, types.ChartRbacResource(0, 0), "read")
|
||||
helpers.AllowMe(h, types.ChartRbacResource(0, 0), "update")
|
||||
helpers.AllowMe(h, types.ChartRbacResource(0, 0), "delete")
|
||||
|
||||
var (
|
||||
ns = h.makeNamespace("some-namespace")
|
||||
ID uint64
|
||||
)
|
||||
|
||||
t.Run("create", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Chart{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/chart/", ns.ID),
|
||||
types.Chart{Labels: map[string]labelTypes.LabelValue{"foo": {Val: "bar"}, "bar": {Val: "42"}}},
|
||||
payload,
|
||||
)
|
||||
req.NotZero(payload.ID)
|
||||
|
||||
h.a.Equal(payload.Labels["foo"].Val, "bar",
|
||||
"labels must contain foo with value bar")
|
||||
h.a.Equal(payload.Labels["bar"].Val, "42",
|
||||
"labels must contain bar with value 42")
|
||||
req.Equal(helpers.LabelsToStrings(payload.Labels), helpers.LabelsToStrings(helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID)),
|
||||
"response must match stored labels")
|
||||
|
||||
ID = payload.ID
|
||||
})
|
||||
|
||||
t.Run("update", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Chart{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/chart/%d", ns.ID, ID),
|
||||
types.Chart{Labels: map[string]labelTypes.LabelValue{"foo": {Val: "baz"}, "baz": {Val: "123"}}},
|
||||
payload,
|
||||
)
|
||||
req.NotZero(payload.ID)
|
||||
req.Nil(payload.UpdatedAt, "updatedAt must not change after changing labels")
|
||||
|
||||
req.Equal(payload.Labels["foo"].Val, "baz",
|
||||
"labels must contain foo with value baz")
|
||||
req.NotContains(payload.Labels, "bar",
|
||||
"labels must not contain bar")
|
||||
req.Equal(payload.Labels["baz"].Val, "123",
|
||||
"labels must contain baz with value 123")
|
||||
req.Equal(helpers.LabelsToStrings(payload.Labels), helpers.LabelsToStrings(helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID)),
|
||||
"response must match stored labels")
|
||||
})
|
||||
|
||||
t.Run("search", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
set = types.ChartSet{}
|
||||
)
|
||||
|
||||
helpers.SearchWithLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/chart/", ns.ID),
|
||||
&set, url.Values{"labels": []string{"baz=123"}},
|
||||
)
|
||||
req.NotEmpty(set)
|
||||
req.NotNil(set.FindByID(ID))
|
||||
req.NotNil(set.FindByID(ID).Labels)
|
||||
})
|
||||
}
|
||||
440
server/tests/compose/main_test.go
Normal file
440
server/tests/compose/main_test.go
Normal file
@@ -0,0 +1,440 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/app"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api/server"
|
||||
"github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/server/pkg/cli"
|
||||
"github.com/cortezaproject/corteza/server/pkg/dal"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoy"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoy/csv"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoy/directory"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoy/resource"
|
||||
envoyStore "github.com/cortezaproject/corteza/server/pkg/envoy/store"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoy/yaml"
|
||||
"github.com/cortezaproject/corteza/server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
"github.com/cortezaproject/corteza/server/pkg/locale"
|
||||
"github.com/cortezaproject/corteza/server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza/server/pkg/objstore/plain"
|
||||
"github.com/cortezaproject/corteza/server/pkg/rand"
|
||||
"github.com/cortezaproject/corteza/server/pkg/rbac"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
sysTypes "github.com/cortezaproject/corteza/server/system/types"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
"github.com/go-chi/chi/v5"
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/steinfletcher/apitest"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type (
|
||||
helper struct {
|
||||
t *testing.T
|
||||
a *require.Assertions
|
||||
|
||||
cUser *sysTypes.User
|
||||
roleID uint64
|
||||
token []byte
|
||||
}
|
||||
|
||||
dalSvc interface {
|
||||
dal.FullService
|
||||
|
||||
Purge(ctx context.Context)
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
testApp *app.CortezaApp
|
||||
r chi.Router
|
||||
testUser *sysTypes.User
|
||||
|
||||
eventBus = eventbus.New()
|
||||
defStore store.Storer
|
||||
defDal dalSvc
|
||||
)
|
||||
|
||||
func init() {
|
||||
helpers.RecursiveDotEnvLoad()
|
||||
}
|
||||
|
||||
// random string, 10 chars long by default
|
||||
func rs(a ...int) string {
|
||||
var l = 10
|
||||
if len(a) > 0 {
|
||||
l = a[0]
|
||||
}
|
||||
|
||||
return string(rand.Bytes(l))
|
||||
}
|
||||
|
||||
func InitTestApp() {
|
||||
if testApp == nil {
|
||||
ctx := cli.Context()
|
||||
|
||||
testApp = helpers.NewIntegrationTestApp(ctx, func(app *app.CortezaApp) (err error) {
|
||||
service.DefaultStore = app.Store
|
||||
defStore = app.Store
|
||||
service.DefaultObjectStore, err = plain.NewWithAfero(afero.NewMemMapFs(), "test")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Tests should be executed w/o any locales
|
||||
locale.SetGlobal(locale.Static(&locale.Language{Tag: language.Und}))
|
||||
|
||||
eventbus.Set(eventBus)
|
||||
return nil
|
||||
})
|
||||
|
||||
defDal = dal.Service()
|
||||
}
|
||||
|
||||
if r == nil {
|
||||
r = chi.NewRouter()
|
||||
r.Use(server.BaseMiddleware(false, logger.Default())...)
|
||||
helpers.BindAuthMiddleware(r)
|
||||
r.Group(rest.MountRoutes())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
InitTestApp()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func newHelper(t *testing.T) helper {
|
||||
ctx := context.Background()
|
||||
|
||||
h := helper{
|
||||
t: t,
|
||||
a: require.New(t),
|
||||
roleID: id.Next(),
|
||||
}
|
||||
|
||||
if testUser == nil {
|
||||
testUser = &sysTypes.User{
|
||||
Handle: fmt.Sprintf("test_user_%d", id.Next()),
|
||||
Name: "test_user",
|
||||
ID: id.Next(),
|
||||
}
|
||||
|
||||
err := store.CreateUser(ctx, service.DefaultStore, testUser)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
h.cUser = testUser
|
||||
|
||||
h.cUser.SetRoles(h.roleID)
|
||||
helpers.UpdateRBAC(h.roleID)
|
||||
h.identityToHelper(h.cUser)
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *helper) identityToHelper(u *sysTypes.User) {
|
||||
var err error
|
||||
h.cUser = u
|
||||
|
||||
ctx := context.Background()
|
||||
h.token, err = auth.TokenIssuer.Issue(ctx, auth.WithIdentity(h.cUser))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h helper) MyRole() uint64 {
|
||||
return h.roleID
|
||||
}
|
||||
|
||||
// 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 {
|
||||
InitTestApp()
|
||||
|
||||
return apitest.
|
||||
New().
|
||||
Handler(r).
|
||||
Intercept(helpers.ReqHeaderRawAuthBearer(h.token))
|
||||
}
|
||||
|
||||
func (h helper) mockPermissions(rules ...*rbac.Rule) {
|
||||
h.noError(rbac.Global().Grant(
|
||||
// TestService we use does not have any backend storage,
|
||||
context.Background(),
|
||||
rules...,
|
||||
))
|
||||
}
|
||||
|
||||
// Unwraps error before it passes it to the tester
|
||||
func (h helper) noError(err error) {
|
||||
for errors.Unwrap(err) != nil {
|
||||
err = errors.Unwrap(err)
|
||||
}
|
||||
|
||||
h.a.NoError(err)
|
||||
}
|
||||
|
||||
func collect(ee ...error) error {
|
||||
for _, e := range ee {
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanup(t *testing.T) {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
err := collect(
|
||||
store.TruncateComposeNamespaces(ctx, defStore),
|
||||
store.TruncateComposePages(ctx, defStore),
|
||||
store.TruncateComposeModuleFields(ctx, defStore),
|
||||
store.TruncateComposeModules(ctx, defStore),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode scenario data: %v", err)
|
||||
}
|
||||
|
||||
err = truncateRecords(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to truncate records: %v", err)
|
||||
}
|
||||
|
||||
defDal.Purge(ctx)
|
||||
}
|
||||
|
||||
func truncateRecords(ctx context.Context) error {
|
||||
models, err := defDal.SearchModels(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, model := range models {
|
||||
err = defDal.Truncate(ctx, model.ToFilter(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func scenario(t *testing.T) string {
|
||||
return t.Name()[5:]
|
||||
}
|
||||
|
||||
func loadScenario(ctx context.Context, s store.Storer, t *testing.T, h helper) {
|
||||
loadScenarioWithName(ctx, s, t, h, scenario(t))
|
||||
}
|
||||
|
||||
func loadScenarioWithName(ctx context.Context, s store.Storer, t *testing.T, h helper, scenario string) {
|
||||
cleanup(t)
|
||||
parseEnvoy(ctx, s, h, path.Join("testdata", scenario, "data_model"))
|
||||
}
|
||||
|
||||
func parseEnvoy(ctx context.Context, s store.Storer, h helper, path string) {
|
||||
nn, err := directory.Decode(
|
||||
ctx,
|
||||
path,
|
||||
yaml.Decoder(),
|
||||
csv.Decoder(),
|
||||
)
|
||||
if err != nil {
|
||||
h.t.Fatalf("failed to decode scenario data: %v", err)
|
||||
}
|
||||
|
||||
crs := resource.ComposeRecordShaper()
|
||||
nn, err = resource.Shape(nn, crs)
|
||||
h.a.NoError(err)
|
||||
|
||||
// import into the store
|
||||
se := envoyStore.NewStoreEncoder(s, dal.Service(), &envoyStore.EncoderConfig{})
|
||||
bld := envoy.NewBuilder(se)
|
||||
g, err := bld.Build(ctx, nn...)
|
||||
h.a.NoError(err)
|
||||
err = envoy.Encode(ctx, g, se)
|
||||
h.a.NoError(err)
|
||||
}
|
||||
|
||||
func bypassRBAC(ctx context.Context) context.Context {
|
||||
u := &sysTypes.User{
|
||||
ID: id.Next(),
|
||||
}
|
||||
|
||||
u.SetRoles(auth.BypassRoles().IDs()...)
|
||||
|
||||
return auth.SetIdentityToContext(ctx, u)
|
||||
}
|
||||
|
||||
func setup(t *testing.T) (context.Context, helper, store.Storer) {
|
||||
h := newHelper(t)
|
||||
s := service.DefaultStore
|
||||
|
||||
u := &sysTypes.User{
|
||||
ID: id.Next(),
|
||||
}
|
||||
u.SetRoles(auth.BypassRoles().IDs()...)
|
||||
|
||||
ctx := auth.SetIdentityToContext(context.Background(), u)
|
||||
return ctx, h, s
|
||||
}
|
||||
|
||||
func grantImportExport(h helper) {
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "namespace.create")
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "module.create")
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "page.create")
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "chart.create")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "modules.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "charts.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "pages.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "read")
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "read")
|
||||
helpers.AllowMe(h, types.ChartRbacResource(0, 0), "read")
|
||||
helpers.AllowMe(h, sysTypes.ComponentRbacResource(), "roles.search")
|
||||
helpers.AllowMe(h, sysTypes.RoleRbacResource(0), "read")
|
||||
}
|
||||
|
||||
func namespaceExportSafe(t *testing.T, h helper, namespaceID uint64) []byte {
|
||||
bb, err := namespaceExport(t, h, namespaceID)
|
||||
h.a.NoError(err)
|
||||
|
||||
return bb
|
||||
}
|
||||
|
||||
func namespaceExport(t *testing.T, h helper, namespaceID uint64) ([]byte, error) {
|
||||
out := h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d/export/out.zip?jwt=%s", namespaceID, h.token)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
End()
|
||||
|
||||
defer out.Response.Body.Close()
|
||||
|
||||
bb, err := ioutil.ReadAll(out.Response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(string(bb), "Error:") {
|
||||
return nil, errors.New(string(bb))
|
||||
}
|
||||
|
||||
return bb, nil
|
||||
}
|
||||
|
||||
func namespaceImportInitPathSafe(t *testing.T, h helper, pp ...string) uint64 {
|
||||
sessionID, err := namespaceImportInitPath(t, h, pp...)
|
||||
h.a.NoError(err)
|
||||
|
||||
return sessionID
|
||||
}
|
||||
|
||||
func namespaceImportInitPath(t *testing.T, h helper, pp ...string) (uint64, error) {
|
||||
f, err := os.Open(testSource(t, pp...))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
bb, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return namespaceImportInit(t, h, bb)
|
||||
}
|
||||
|
||||
func namespaceImportInitSafe(t *testing.T, h helper, arch []byte) uint64 {
|
||||
sessionID, err := namespaceImportInit(t, h, arch)
|
||||
h.a.NoError(err)
|
||||
|
||||
return sessionID
|
||||
}
|
||||
|
||||
func namespaceImportInit(t *testing.T, h helper, arch []byte) (uint64, error) {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
part, err := writer.CreateFormFile("upload", "archive.zip")
|
||||
h.noError(err)
|
||||
|
||||
_, err = part.Write(arch)
|
||||
h.noError(err)
|
||||
h.noError(writer.Close())
|
||||
|
||||
out := h.apiInit().
|
||||
Post("/namespace/import").
|
||||
Header("Accept", "application/json").
|
||||
Body(body.String()).
|
||||
ContentType(writer.FormDataContentType()).
|
||||
Expect(h.t).
|
||||
Status(http.StatusOK).
|
||||
End()
|
||||
|
||||
defer out.Response.Body.Close()
|
||||
bb, err := ioutil.ReadAll(out.Response.Body)
|
||||
h.a.NoError(err)
|
||||
|
||||
var aux struct {
|
||||
Error struct {
|
||||
Message string
|
||||
}
|
||||
Response struct {
|
||||
ID uint64 `json:"sessionID,string"`
|
||||
}
|
||||
}
|
||||
h.a.NoError(json.Unmarshal(bb, &aux))
|
||||
|
||||
if aux.Error.Message != "" {
|
||||
return 0, errors.New(aux.Error.Message)
|
||||
}
|
||||
|
||||
return aux.Response.ID, nil
|
||||
}
|
||||
|
||||
func namespaceImportRun(ctx context.Context, s store.Storer, t *testing.T, h helper, sessionID uint64, name, slug string) (*types.Namespace, types.ModuleSet, types.PageSet, types.ChartSet) {
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/import/%d", sessionID)).
|
||||
Header("Accept", "application/json").
|
||||
FormData("name", name).
|
||||
FormData("slug", slug).
|
||||
Expect(h.t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
ns, mm, pp, cc, _, err := fetchEntireNamespace(ctx, s, slug)
|
||||
h.a.NoError(err)
|
||||
|
||||
return ns, mm, pp, cc
|
||||
}
|
||||
|
||||
func testSource(t *testing.T, pp ...string) string {
|
||||
return path.Join(append([]string{"testdata", scenario(t)}, pp...)...)
|
||||
}
|
||||
954
server/tests/compose/module_test.go
Normal file
954
server/tests/compose/module_test.go
Normal file
@@ -0,0 +1,954 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
labelTypes "github.com/cortezaproject/corteza/server/pkg/label/types"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
jsonpath "github.com/steinfletcher/apitest-jsonpath"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func (h helper) clearModules() {
|
||||
h.clearNamespaces()
|
||||
h.noError(store.TruncateComposeModules(context.Background(), service.DefaultStore))
|
||||
h.noError(store.TruncateComposeModuleFields(context.Background(), service.DefaultStore))
|
||||
|
||||
models, err := defDal.SearchModels(context.Background())
|
||||
h.noError(err)
|
||||
for _, m := range models {
|
||||
h.noError(defDal.RemoveModel(context.Background(), m.ConnectionID, m.ResourceID))
|
||||
}
|
||||
}
|
||||
|
||||
func (h helper) makeModule(ns *types.Namespace, name string, ff ...*types.ModuleField) *types.Module {
|
||||
return h.createModule(ns, &types.Module{
|
||||
Name: name,
|
||||
NamespaceID: ns.ID,
|
||||
Fields: ff,
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
func (h helper) createModule(ns *types.Namespace, res *types.Module) *types.Module {
|
||||
res.ID = id.Next()
|
||||
res.CreatedAt = time.Now()
|
||||
h.noError(store.CreateComposeModule(context.Background(), service.DefaultStore, res))
|
||||
|
||||
if res.Config.DAL.ConnectionID == 0 {
|
||||
res.Config.DAL.ConnectionID = defDal.GetConnectionByID(0).ID
|
||||
}
|
||||
|
||||
_ = res.Fields.Walk(func(f *types.ModuleField) error {
|
||||
f.ID = id.Next()
|
||||
f.ModuleID = res.ID
|
||||
f.NamespaceID = res.NamespaceID
|
||||
f.CreatedAt = time.Now()
|
||||
return nil
|
||||
})
|
||||
|
||||
h.noError(store.CreateComposeModuleField(context.Background(), service.DefaultStore, res.Fields...))
|
||||
|
||||
h.noError(service.DalModelReplace(context.Background(), service.DefaultStore, nil, defDal, ns, res))
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (h helper) lookupModuleByID(ID uint64) *types.Module {
|
||||
res, err := store.LookupComposeModuleByID(context.Background(), service.DefaultStore, ID)
|
||||
h.noError(err)
|
||||
|
||||
res.Fields, _, err = store.SearchComposeModuleFields(context.Background(), service.DefaultStore, types.ModuleFieldFilter{ModuleID: []uint64{ID}})
|
||||
h.noError(err)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func TestModuleRead(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "read")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
m := h.makeModule(ns, "some-module")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Equal(`$.response.name`, m.Name)).
|
||||
Assert(jsonpath.Equal(`$.response.moduleID`, fmt.Sprintf("%d", m.ID))).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestModuleReadByHandle(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "read")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
c := h.makeModule(ns, "some-module")
|
||||
|
||||
cbh, err := service.DefaultModule.FindByHandle(h.secCtx(), ns.ID, c.Handle)
|
||||
|
||||
h.noError(err)
|
||||
h.a.NotNil(cbh)
|
||||
h.a.Equal(cbh.ID, c.ID)
|
||||
h.a.Equal(cbh.Handle, c.Handle)
|
||||
}
|
||||
|
||||
func TestModuleList(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
h.makeModule(ns, "app")
|
||||
h.makeModule(ns, "app")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d/module/", ns.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestModuleListQuery(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
h.createModule(ns, &types.Module{
|
||||
Name: "name",
|
||||
Handle: "handle",
|
||||
NamespaceID: ns.ID,
|
||||
})
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d/module/", ns.ID)).
|
||||
Query("query", "handle").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.set != null")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestModuleList_filterForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
h.makeModule(ns, "module")
|
||||
f := h.makeModule(ns, "module_forbidden")
|
||||
|
||||
helpers.DenyMe(h, types.ModuleRbacResource(0, f.ID), "read")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d/module/", ns.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.NotPresent(`$.response.set[? @.name=="module_forbidden"]`)).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestModuleCreateForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/", ns.ID)).
|
||||
Header("Accept", "application/json").
|
||||
FormData("name", "some-module").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("module.errors.notAllowedToCreate")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestModuleCreate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "module.create")
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/", ns.ID)).
|
||||
FormData("name", "some-module").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestModuleCreateInvalidField(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "module.create")
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/", ns.ID)).
|
||||
JSON(`{ "name": "mod", "fields": [{ "name": "a", "kind": "Number" }] }`).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("module.errors.invalidHandle")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestModuleUpdateForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
m := h.makeModule(ns, "some-module")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
Header("Accept", "application/json").
|
||||
FormData("name", "changed-name").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("module.errors.notAllowedToUpdate")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestModuleUpdate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
m := h.makeModule(ns, "some-module")
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
FormData("name", "changed-name").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.updatedAt")).
|
||||
End()
|
||||
|
||||
m, err := store.LookupComposeModuleByID(context.Background(), service.DefaultStore, m.ID)
|
||||
h.noError(err)
|
||||
h.a.NotNil(m)
|
||||
h.a.Equal("changed-name", m.Name)
|
||||
}
|
||||
|
||||
func TestModuleFieldsUpdate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
m := h.makeModule(ns, "some-module", &types.ModuleField{ID: id.Next(), Kind: "String", Name: "existing"})
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
||||
|
||||
f := m.Fields[0]
|
||||
fjs := fmt.Sprintf(`{ "name": "%s", "fields": [{ "fieldID": "%d", "name": "existing_edited", "kind": "Number" }, { "name": "new", "kind": "DateTime" }] }`, m.Name, f.ID)
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
JSON(fjs).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
m = h.lookupModuleByID(m.ID)
|
||||
h.a.NotNil(m)
|
||||
h.a.NotNil(m.Fields)
|
||||
h.a.Len(m.Fields, 2)
|
||||
|
||||
h.a.NotNil(m.Fields[0].UpdatedAt)
|
||||
h.a.Equal(m.Fields[0].Name, "existing_edited")
|
||||
h.a.Equal(m.Fields[0].Kind, "Number")
|
||||
h.a.Nil(m.Fields[1].UpdatedAt)
|
||||
h.a.Equal(m.Fields[1].Name, "new")
|
||||
h.a.Equal(m.Fields[1].Kind, "DateTime")
|
||||
}
|
||||
|
||||
func TestModuleFieldsUpdate_invalidHandle(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "module.create")
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
mod := h.makeModule(ns, "mod", &types.ModuleField{
|
||||
Name: "a",
|
||||
Kind: "String",
|
||||
})
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, mod.ID)).
|
||||
JSON(`{ "name": "mod", "fields": [{ "name": "a", "kind": "String" }] }`).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestModuleUpdateWithReservedFieldName(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
m := h.makeModule(ns, "some-module", &types.ModuleField{ID: id.Next(), Kind: "String", Name: "ownedBy"})
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
||||
|
||||
f := m.Fields[0]
|
||||
fjs := fmt.Sprintf(`{ "name": "%s", "fields": [{ "fieldID": "%d", "name": "ownedBy", "kind": "Number" }]}`, m.Name, f.ID)
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
Header("Accept", "application/json").
|
||||
JSON(fjs).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
f = m.Fields[0]
|
||||
fjs = fmt.Sprintf(`{ "name": "%s", "fields": [{ "fieldID": "%d", "name": "updatedBy", "kind": "Number" }]}`, m.Name, f.ID)
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
Header("Accept", "application/json").
|
||||
JSON(fjs).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("module.errors.fieldNameReserved")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestModuleCreateWithReservedFieldName(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
||||
|
||||
fjs := fmt.Sprintf(`{ "name": "foo", "fields": [{ "name": "ownedBy", "kind": "Number" }]}`)
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/", ns.ID)).
|
||||
Header("Accept", "application/json").
|
||||
JSON(fjs).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("module.errors.fieldNameReserved")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestModuleFieldsUpdate_defaults(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
m := h.makeModule(ns, "some-module", &types.ModuleField{ID: id.Next(), Kind: "String", Name: "existing", Required: true, DefaultValue: types.RecordValueSet{&types.RecordValue{Value: "test"}}})
|
||||
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
||||
|
||||
f := m.Fields[0]
|
||||
fjs := fmt.Sprintf(`{ "name": "%s", "fields": [{ "fieldID": "%d", "name": "existing_edited", "kind": "String", "isRequired": true, "defaultValue": [{ "value": "test" }] }, { "name": "new", "kind": "Bool", "defaultValue": [{ "value": "1" }] }] }`, m.Name, f.ID)
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
JSON(fjs).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
m = h.lookupModuleByID(m.ID)
|
||||
h.a.NotNil(m)
|
||||
h.a.NotNil(m.Fields)
|
||||
h.a.Len(m.Fields, 2)
|
||||
|
||||
h.a.Equal(m.Fields[0].Kind, "String")
|
||||
h.a.Len(m.Fields[0].DefaultValue, 1)
|
||||
h.a.Equal("test", m.Fields[0].DefaultValue[0].Value)
|
||||
|
||||
h.a.Equal(m.Fields[1].Kind, "Bool")
|
||||
h.a.Len(m.Fields[1].DefaultValue, 1)
|
||||
h.a.Equal("1", m.Fields[1].DefaultValue[0].Value)
|
||||
}
|
||||
|
||||
func TestModuleFieldsDefaultValue(t *testing.T) {
|
||||
var ns *types.Namespace
|
||||
|
||||
h := newHelper(t)
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
|
||||
prep := func() {
|
||||
h.clearModules()
|
||||
ns = h.makeNamespace("some-namespace")
|
||||
}
|
||||
|
||||
t.Run("boolean; true", func(t *testing.T) {
|
||||
prep()
|
||||
|
||||
m := h.makeModule(ns, "some-module", &types.ModuleField{ID: id.Next(), Kind: "Boolean", Name: "boolean"})
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
||||
|
||||
f := m.Fields[0]
|
||||
fjs := fmt.Sprintf(`{ "name": "%s", "fields": [{ "fieldID": "%d", "name": "boolean", "kind": "Boolean", "defaultValue": [{"name": "boolean", "value": "1"}] }] }`, m.Name, f.ID)
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
JSON(fjs).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
m = h.lookupModuleByID(m.ID)
|
||||
h.a.NotNil(m)
|
||||
h.a.NotNil(m.Fields)
|
||||
h.a.Len(m.Fields, 1)
|
||||
|
||||
h.a.NotNil(m.Fields[0].DefaultValue)
|
||||
h.a.Len(m.Fields[0].DefaultValue, 1)
|
||||
h.a.Equal("1", m.Fields[0].DefaultValue[0].Value)
|
||||
})
|
||||
|
||||
t.Run("boolean; false", func(t *testing.T) {
|
||||
prep()
|
||||
|
||||
m := h.makeModule(ns, "some-module", &types.ModuleField{ID: id.Next(), Kind: "Boolean", Name: "boolean"})
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
||||
|
||||
f := m.Fields[0]
|
||||
fjs := fmt.Sprintf(`{ "name": "%s", "fields": [{ "fieldID": "%d", "name": "boolean", "kind": "Boolean", "defaultValue": [{"name": "boolean", "value": ""}] }] }`, m.Name, f.ID)
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
JSON(fjs).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
m = h.lookupModuleByID(m.ID)
|
||||
h.a.NotNil(m)
|
||||
h.a.NotNil(m.Fields)
|
||||
h.a.Len(m.Fields, 1)
|
||||
|
||||
h.a.NotNil(m.Fields[0].DefaultValue)
|
||||
h.a.Len(m.Fields[0].DefaultValue, 1)
|
||||
h.a.Equal("", m.Fields[0].DefaultValue[0].Value)
|
||||
})
|
||||
|
||||
t.Run("boolean; undefined", func(t *testing.T) {
|
||||
prep()
|
||||
|
||||
m := h.makeModule(ns, "some-module", &types.ModuleField{ID: id.Next(), Kind: "Boolean", Name: "boolean"})
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
||||
|
||||
f := m.Fields[0]
|
||||
fjs := fmt.Sprintf(`{ "name": "%s", "fields": [{ "fieldID": "%d", "name": "boolean", "kind": "Boolean" }] }`, m.Name, f.ID)
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
JSON(fjs).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
m = h.lookupModuleByID(m.ID)
|
||||
h.a.NotNil(m)
|
||||
h.a.NotNil(m.Fields)
|
||||
h.a.Len(m.Fields, 1)
|
||||
|
||||
h.a.Nil(m.Fields[0].DefaultValue)
|
||||
h.a.Len(m.Fields[0].DefaultValue, 0)
|
||||
})
|
||||
|
||||
t.Run("boolean; true; compact form", func(t *testing.T) {
|
||||
prep()
|
||||
|
||||
m := h.makeModule(ns, "some-module", &types.ModuleField{ID: id.Next(), Kind: "Boolean", Name: "boolean"})
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
||||
|
||||
f := m.Fields[0]
|
||||
fjs := fmt.Sprintf(`{ "name": "%s", "fields": [{ "fieldID": "%d", "name": "boolean", "kind": "Boolean", "defaultValue": [{"value": "1"}] }] }`, m.Name, f.ID)
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
JSON(fjs).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
m = h.lookupModuleByID(m.ID)
|
||||
h.a.NotNil(m)
|
||||
h.a.NotNil(m.Fields)
|
||||
h.a.Len(m.Fields, 1)
|
||||
|
||||
h.a.NotNil(m.Fields[0].DefaultValue)
|
||||
h.a.Len(m.Fields[0].DefaultValue, 1)
|
||||
h.a.Equal("1", m.Fields[0].DefaultValue[0].Value)
|
||||
})
|
||||
|
||||
t.Run("record; valid", func(t *testing.T) {
|
||||
prep()
|
||||
|
||||
// Prep the related module
|
||||
refM := h.makeModule(ns, "ref-mod", &types.ModuleField{
|
||||
ID: id.Next(),
|
||||
Kind: "String",
|
||||
Name: "string",
|
||||
})
|
||||
rec := h.makeRecord(refM, &types.RecordValue{
|
||||
Name: "string",
|
||||
Value: "val",
|
||||
})
|
||||
|
||||
// Prep the module and a field
|
||||
m := h.makeModule(ns, "some-module", &types.ModuleField{
|
||||
ID: id.Next(),
|
||||
Kind: "String",
|
||||
Name: "string",
|
||||
})
|
||||
|
||||
// RBAC (make sure to allow record read)
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
||||
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "read")
|
||||
|
||||
f := m.Fields[0]
|
||||
fjs := fmt.Sprintf(`{ "name": "%s", "fields": [{ "fieldID": "%d", "name": "record", "kind": "Record", "options": {"moduleID": "%d"}, "defaultValue": [{"value": "%d"}] }] }`, m.Name, f.ID, refM.ID, rec.ID)
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
JSON(fjs).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
m = h.lookupModuleByID(m.ID)
|
||||
h.a.NotNil(m)
|
||||
h.a.NotNil(m.Fields)
|
||||
h.a.Len(m.Fields, 1)
|
||||
|
||||
h.a.NotNil(m.Fields[0].DefaultValue)
|
||||
h.a.Len(m.Fields[0].DefaultValue, 1)
|
||||
h.a.Equal(strconv.FormatUint(rec.ID, 10), m.Fields[0].DefaultValue[0].Value)
|
||||
})
|
||||
}
|
||||
|
||||
func TestModuleFieldsUpdate_removed(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
m := h.makeModule(ns, "some-module", &types.ModuleField{ID: id.Next(), Kind: "String", Name: "a"}, &types.ModuleField{ID: id.Next(), Kind: "String", Name: "b"})
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
||||
|
||||
f := m.Fields[0]
|
||||
fjs := fmt.Sprintf(`{ "name": "%s", "fields": [{ "fieldID": "%d", "name": "a", "kind": "String" }] }`, m.Name, f.ID)
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
JSON(fjs).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
m = h.lookupModuleByID(m.ID)
|
||||
h.a.NotNil(m)
|
||||
h.a.NotNil(m.Fields)
|
||||
h.a.Len(m.Fields, 1)
|
||||
|
||||
h.a.NotNil(m.Fields[0].UpdatedAt)
|
||||
h.a.Equal(m.Fields[0].Name, "a")
|
||||
}
|
||||
|
||||
func TestModuleFieldsUpdate_removedHasRecords(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
m := h.makeModule(ns, "some-module", &types.ModuleField{ID: id.Next(), Kind: "String", Name: "a"}, &types.ModuleField{ID: id.Next(), Kind: "String", Name: "b"})
|
||||
h.makeRecord(m, &types.RecordValue{Name: "a", Value: "va"}, &types.RecordValue{Name: "b", Value: "vb"})
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
||||
|
||||
f := m.Fields[0]
|
||||
fjs := fmt.Sprintf(`{ "name": "%s", "fields": [{ "fieldID": "%d", "name": "a", "kind": "String" }] }`, m.Name, f.ID)
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
JSON(fjs).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
m = h.lookupModuleByID(m.ID)
|
||||
h.a.NotNil(m)
|
||||
h.a.NotNil(m.Fields)
|
||||
h.a.Len(m.Fields, 1)
|
||||
|
||||
h.a.NotNil(m.Fields[0].UpdatedAt)
|
||||
h.a.Equal(m.Fields[0].Name, "a")
|
||||
}
|
||||
|
||||
func TestModuleFieldsUpdateExpressions(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "module.create")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "read")
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
||||
|
||||
var (
|
||||
m = &types.Module{
|
||||
NamespaceID: ns.ID,
|
||||
}
|
||||
|
||||
f = &types.ModuleField{
|
||||
ID: id.Next(),
|
||||
Kind: "String",
|
||||
Name: "existing",
|
||||
Expressions: types.ModuleFieldExpr{
|
||||
ValueExpr: `"foo"`,
|
||||
Sanitizers: []string{"value"},
|
||||
Validators: []types.ModuleFieldValidator{
|
||||
{
|
||||
Test: `value != ""`,
|
||||
Error: "error",
|
||||
},
|
||||
},
|
||||
DisableDefaultValidators: true,
|
||||
Formatters: []string{`"foo"`},
|
||||
DisableDefaultFormatters: false,
|
||||
},
|
||||
}
|
||||
|
||||
aux = struct{ Response types.Module }{}
|
||||
)
|
||||
|
||||
m.Fields = append(m.Fields, f)
|
||||
|
||||
// create module
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/", ns.ID)).
|
||||
JSON(helpers.JSON(m)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End().
|
||||
JSON(&aux)
|
||||
|
||||
m = &aux.Response
|
||||
|
||||
h.a.NotEmpty(m.Fields)
|
||||
f = m.Fields.FindByName("existing")
|
||||
h.a.NotNil(f)
|
||||
h.a.Equal(`"foo"`, f.Expressions.ValueExpr)
|
||||
h.a.NotEmpty(f.Expressions.Sanitizers)
|
||||
h.a.NotEmpty(f.Expressions.Validators)
|
||||
h.a.True(f.Expressions.DisableDefaultValidators)
|
||||
h.a.NotEmpty(f.Expressions.Formatters)
|
||||
h.a.False(f.Expressions.DisableDefaultFormatters)
|
||||
|
||||
f.Expressions = types.ModuleFieldExpr{
|
||||
ValueExpr: `"bar"`,
|
||||
Sanitizers: []string{`""`},
|
||||
Validators: []types.ModuleFieldValidator{
|
||||
{
|
||||
Test: `value == ""`,
|
||||
Error: "foo",
|
||||
},
|
||||
},
|
||||
DisableDefaultValidators: false,
|
||||
Formatters: []string{`"bar"`},
|
||||
DisableDefaultFormatters: true,
|
||||
}
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
JSON(helpers.JSON(m)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End().
|
||||
JSON(&aux)
|
||||
|
||||
h.a.NotEmpty(aux.Response.Fields)
|
||||
f = aux.Response.Fields.FindByName("existing")
|
||||
h.a.NotNil(f)
|
||||
h.a.Equal(`"bar"`, f.Expressions.ValueExpr)
|
||||
h.a.NotEmpty(f.Expressions.Sanitizers)
|
||||
h.a.NotEmpty(f.Expressions.Validators)
|
||||
h.a.False(f.Expressions.DisableDefaultValidators)
|
||||
h.a.NotEmpty(f.Expressions.Formatters)
|
||||
h.a.True(f.Expressions.DisableDefaultFormatters)
|
||||
}
|
||||
|
||||
func TestModuleFieldsPreventUpdate_ifRecordExists(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
m := h.makeModule(ns, "some-module", &types.ModuleField{ID: id.Next(), Kind: "String", Name: "existing"})
|
||||
h.makeRecord(m, &types.RecordValue{Name: "existing", Value: "value"})
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
||||
|
||||
f := m.Fields[0]
|
||||
fjs := fmt.Sprintf(`{ "name": "%s", "fields": [{ "fieldID": "%d", "name": "existing_edited", "kind": "Number" }, { "name": "new", "kind": "DateTime" }] }`, m.Name, f.ID)
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
JSON(fjs).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
m = h.lookupModuleByID(m.ID)
|
||||
h.a.NotNil(m)
|
||||
h.a.NotNil(m.Fields)
|
||||
h.a.Len(m.Fields, 2)
|
||||
|
||||
h.a.NotNil(m.Fields[0].UpdatedAt)
|
||||
h.a.Equal(m.Fields[0].Name, "existing")
|
||||
h.a.Equal(m.Fields[0].Kind, "String")
|
||||
h.a.Nil(m.Fields[1].UpdatedAt)
|
||||
h.a.Equal(m.Fields[1].Name, "new")
|
||||
h.a.Equal(m.Fields[1].Kind, "DateTime")
|
||||
}
|
||||
|
||||
func TestModuleDeleteForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "read")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
m := h.makeModule(ns, "some-module")
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, m.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("module.errors.notAllowedToDelete")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestModuleDelete(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "read")
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "delete")
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
res := h.makeModule(ns, "some-module")
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, res.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
res = h.lookupModuleByID(res.ID)
|
||||
h.a.NotNil(res.DeletedAt)
|
||||
}
|
||||
|
||||
func TestModuleLabels(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearModules()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "modules.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "module.create")
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "read")
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "update")
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "delete")
|
||||
|
||||
var (
|
||||
ns = h.makeNamespace("some-namespace")
|
||||
fieldID, ID uint64
|
||||
)
|
||||
|
||||
t.Run("create", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Module{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/module/", ns.ID),
|
||||
types.Module{Labels: map[string]labelTypes.LabelValue{"foo": {Val: "bar"}, "bar": {Val: "42"}}},
|
||||
payload,
|
||||
)
|
||||
req.NotZero(payload.ID)
|
||||
|
||||
h.a.Equal(payload.Labels["foo"].Val, "bar",
|
||||
"labels must contain foo with value bar")
|
||||
h.a.Equal(payload.Labels["bar"].Val, "42",
|
||||
"labels must contain bar with value 42")
|
||||
req.Equal(helpers.LabelsToStrings(payload.Labels), helpers.LabelsToStrings(helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID)),
|
||||
"response must match stored labels")
|
||||
|
||||
ID = payload.ID
|
||||
})
|
||||
|
||||
t.Run("update", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Module{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/module/%d", ns.ID, ID),
|
||||
types.Module{Labels: map[string]labelTypes.LabelValue{"foo": {Val: "baz"}, "baz": {Val: "123"}}},
|
||||
payload,
|
||||
)
|
||||
req.NotZero(payload.ID)
|
||||
req.Nil(payload.UpdatedAt, "updatedAt must not change after changing labels")
|
||||
|
||||
req.Equal(payload.Labels["foo"].Val, "baz",
|
||||
"labels must contain foo with value baz")
|
||||
req.NotContains(payload.Labels, "bar",
|
||||
"labels must not contain bar")
|
||||
req.Equal(payload.Labels["baz"].Val, "123",
|
||||
"labels must contain baz with value 123")
|
||||
req.Equal(helpers.LabelsToStrings(payload.Labels), helpers.LabelsToStrings(helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID)),
|
||||
"response must match stored labels")
|
||||
})
|
||||
|
||||
t.Run("search", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
set = types.ModuleSet{}
|
||||
)
|
||||
|
||||
helpers.SearchWithLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/module/", ns.ID),
|
||||
&set,
|
||||
url.Values{"labels": []string{"baz=123"}},
|
||||
)
|
||||
req.NotEmpty(set)
|
||||
req.NotNil(set.FindByID(ID).Labels)
|
||||
})
|
||||
|
||||
t.Run("field create", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
req = require.New(t)
|
||||
mod, err = store.LookupComposeModuleByID(ctx, service.DefaultStore, ID)
|
||||
|
||||
payload = struct{ Response *types.Module }{}
|
||||
)
|
||||
|
||||
req.NoError(err)
|
||||
req.NotNil(mod)
|
||||
mod.Fields = append(mod.Fields, &types.ModuleField{
|
||||
Kind: "String",
|
||||
Name: "labeled",
|
||||
Label: "",
|
||||
Labels: map[string]labelTypes.LabelValue{"fldfoo": {Val: "fldbar"}},
|
||||
})
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, ID)).
|
||||
JSON(helpers.JSON(mod)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Equal(`$.response.fields[0].labels.fldfoo`, "fldbar")).
|
||||
End().
|
||||
JSON(&payload)
|
||||
|
||||
fieldID = payload.Response.Fields[0].ID
|
||||
|
||||
})
|
||||
|
||||
t.Run("field update", func(t *testing.T) {
|
||||
if fieldID == 0 {
|
||||
t.Skip("label/field create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
req = require.New(t)
|
||||
mod, err = store.LookupComposeModuleByID(ctx, service.DefaultStore, ID)
|
||||
)
|
||||
|
||||
req.NoError(err)
|
||||
req.NotNil(mod)
|
||||
mod.Fields = append(mod.Fields, &types.ModuleField{
|
||||
ID: fieldID,
|
||||
Kind: "String",
|
||||
Name: "labeled",
|
||||
Label: "",
|
||||
Labels: map[string]labelTypes.LabelValue{"fldfoo": {Val: "fldbaz"}},
|
||||
})
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d", ns.ID, ID)).
|
||||
JSON(helpers.JSON(mod)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Equal(`$.response.fields[0].labels.fldfoo`, "fldbaz")).
|
||||
End()
|
||||
})
|
||||
}
|
||||
171
server/tests/compose/namespace_duplicate_test.go
Normal file
171
server/tests/compose/namespace_duplicate_test.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
sysTypes "github.com/cortezaproject/corteza/server/system/types"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
func fetchEntireNamespace(ctx context.Context, s store.Storer, slug string) (ns *types.Namespace, mm types.ModuleSet, pp types.PageSet, cc types.ChartSet, slg string, err error) {
|
||||
slg = slug
|
||||
|
||||
ns, err = store.LookupComposeNamespaceBySlug(ctx, s, slug)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mm, _, err = store.SearchComposeModules(ctx, s, types.ModuleFilter{NamespaceID: ns.ID})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < len(mm); i++ {
|
||||
mm[i].Fields, _, err = store.SearchComposeModuleFields(ctx, s, types.ModuleFieldFilter{ModuleID: []uint64{mm[i].ID}})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pp, _, err = store.SearchComposePages(ctx, s, types.PageFilter{NamespaceID: ns.ID})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cc, _, err = store.SearchComposeCharts(ctx, s, types.ChartFilter{NamespaceID: ns.ID})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func findModuleByHandle(mm types.ModuleSet, h string) *types.Module {
|
||||
for _, m := range mm {
|
||||
if m.Handle == h {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findChartByHandle(cc types.ChartSet, h string) *types.Chart {
|
||||
for _, m := range cc {
|
||||
if m.Handle == h {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findPageByHandle(pp types.PageSet, h string) *types.Page {
|
||||
for _, m := range pp {
|
||||
if m.Handle == h {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Test_namespace_duplicate(t *testing.T) {
|
||||
ctx, h, s := setup(t)
|
||||
loadScenario(ctx, defStore, t, h)
|
||||
ns, _, _, _, _, err := fetchEntireNamespace(ctx, s, "ns1")
|
||||
h.a.NoError(err)
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "namespace.create")
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "module.create")
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "page.create")
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "chart.create")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "modules.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "charts.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "pages.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "export")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "modules.export")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "charts.export")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "read")
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "read")
|
||||
helpers.AllowMe(h, types.ChartRbacResource(0, 0), "read")
|
||||
helpers.AllowMe(h, sysTypes.ComponentRbacResource(), "roles.search")
|
||||
helpers.AllowMe(h, sysTypes.RoleRbacResource(0), "read")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/clone", ns.ID)).
|
||||
JSON(`{ "slug": "cloned", "name": "cloned name" }`).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
usedIDs := make(map[uint64]bool)
|
||||
checkID := func(h helper, id uint64) {
|
||||
if usedIDs[id] {
|
||||
h.a.FailNow(fmt.Sprintf("the ID is not unique across cloned resources: %d", id))
|
||||
}
|
||||
usedIDs[id] = true
|
||||
}
|
||||
|
||||
checker := func(ns *types.Namespace, mm types.ModuleSet, pp types.PageSet, cc types.ChartSet, slug string, err error) {
|
||||
h.a.NoError(err)
|
||||
|
||||
// NS
|
||||
h.a.Equal(slug, ns.Slug)
|
||||
|
||||
// Modules
|
||||
mod1 := findModuleByHandle(mm, "mod1")
|
||||
h.a.NotNil(mod1)
|
||||
checkID(h, mod1.ID)
|
||||
mod2 := findModuleByHandle(mm, "mod2")
|
||||
h.a.NotNil(mod2)
|
||||
checkID(h, mod2.ID)
|
||||
mod3 := findModuleByHandle(mm, "mod3")
|
||||
h.a.NotNil(mod3)
|
||||
checkID(h, mod3.ID)
|
||||
|
||||
h.a.Len(mod1.Fields, 4)
|
||||
h.a.Equal("Record", mod1.Fields.FindByName("f4").Kind)
|
||||
h.a.Equal(mod2.ID, mod1.Fields.FindByName("f4").Options.UInt64("moduleID"))
|
||||
|
||||
h.a.Len(mod2.Fields, 4)
|
||||
h.a.Equal("Record", mod2.Fields.FindByName("f_ref_self").Kind)
|
||||
h.a.Equal(mod2.ID, mod2.Fields.FindByName("f_ref_self").Options.UInt64("moduleID"))
|
||||
h.a.Equal("Record", mod2.Fields.FindByName("f2").Kind)
|
||||
h.a.Equal(mod3.ID, mod2.Fields.FindByName("f2").Options.UInt64("moduleID"))
|
||||
|
||||
h.a.Len(mod3.Fields, 1)
|
||||
|
||||
// Charts
|
||||
chr1 := findChartByHandle(cc, "chr1")
|
||||
checkID(h, chr1.ID)
|
||||
h.a.NotNil(chr1)
|
||||
h.a.Len(chr1.Config.Reports, 1)
|
||||
h.a.Equal(mod1.ID, chr1.Config.Reports[0].ModuleID)
|
||||
|
||||
// Pages
|
||||
pg1 := findPageByHandle(pp, "pg1")
|
||||
checkID(h, pg1.ID)
|
||||
h.a.NotNil(pg1)
|
||||
rpg2 := findPageByHandle(pp, "rpg2")
|
||||
checkID(h, rpg2.ID)
|
||||
h.a.NotNil(rpg2)
|
||||
|
||||
h.a.Len(pg1.Blocks, 3)
|
||||
|
||||
h.a.Equal("RecordList", pg1.Blocks[1].Kind)
|
||||
h.a.Equal(mod1.ID, cast.ToUint64(pg1.Blocks[1].Options["moduleID"]))
|
||||
h.a.Equal("Chart", pg1.Blocks[2].Kind)
|
||||
h.a.Equal(chr1.ID, cast.ToUint64(pg1.Blocks[2].Options["chartID"]))
|
||||
|
||||
h.a.Equal(rpg2.ModuleID, mod1.ID)
|
||||
}
|
||||
|
||||
checker(fetchEntireNamespace(ctx, s, "ns1"))
|
||||
checker(fetchEntireNamespace(ctx, s, "cloned"))
|
||||
}
|
||||
36
server/tests/compose/namespace_export_automation_test.go
Normal file
36
server/tests/compose/namespace_export_automation_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
)
|
||||
|
||||
func Test_namespace_export_automation(t *testing.T) {
|
||||
ctx, h, s := setup(t)
|
||||
loadScenario(ctx, defStore, t, h)
|
||||
grantImportExport(h)
|
||||
|
||||
ns, _, _, _, _, err := fetchEntireNamespace(ctx, s, "ns1")
|
||||
h.a.NoError(err)
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "export")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "modules.export")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "charts.export")
|
||||
|
||||
arch := namespaceExportSafe(t, h, ns.ID)
|
||||
sessionID := namespaceImportInitSafe(t, h, arch)
|
||||
ns, _, pp, _ := namespaceImportRun(ctx, s, t, h, sessionID, "imported", "imported")
|
||||
|
||||
h.a.Equal("imported", ns.Slug)
|
||||
h.a.NotEqual(0, ns.ID)
|
||||
|
||||
p := pp.FindByHandle("pg1")
|
||||
h.a.Len(p.Blocks, 2)
|
||||
for _, b := range p.Blocks {
|
||||
h.a.NotEqual("Automation", b.Kind)
|
||||
}
|
||||
|
||||
cleanup(t)
|
||||
}
|
||||
34
server/tests/compose/namespace_export_empty_test.go
Normal file
34
server/tests/compose/namespace_export_empty_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
)
|
||||
|
||||
func Test_namespace_export_empty(t *testing.T) {
|
||||
ctx, h, s := setup(t)
|
||||
loadScenario(ctx, defStore, t, h)
|
||||
grantImportExport(h)
|
||||
|
||||
ns, _, _, _, _, err := fetchEntireNamespace(ctx, s, "ns1")
|
||||
h.a.NoError(err)
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "export")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "modules.export")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "charts.export")
|
||||
|
||||
arch := namespaceExportSafe(t, h, ns.ID)
|
||||
sessionID := namespaceImportInitSafe(t, h, arch)
|
||||
ns, mm, pp, cc := namespaceImportRun(ctx, s, t, h, sessionID, "imported", "imported")
|
||||
|
||||
h.a.Equal("imported", ns.Slug)
|
||||
h.a.NotEqual(0, ns.ID)
|
||||
|
||||
h.a.Len(mm, 0)
|
||||
h.a.Len(pp, 0)
|
||||
h.a.Len(cc, 0)
|
||||
|
||||
cleanup(t)
|
||||
}
|
||||
26
server/tests/compose/namespace_export_missing_res_test.go
Normal file
26
server/tests/compose/namespace_export_missing_res_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
)
|
||||
|
||||
func Test_namespace_export_missing_res(t *testing.T) {
|
||||
t.Skip("skipping due to no longer erroring out; to be addressed")
|
||||
|
||||
ctx, h, s := setup(t)
|
||||
loadScenario(ctx, defStore, t, h)
|
||||
grantImportExport(h)
|
||||
|
||||
ns, mm, _, _, _, err := fetchEntireNamespace(ctx, s, "ns1")
|
||||
h.a.NoError(err)
|
||||
|
||||
// Removeing one of the resources
|
||||
h.a.NoError(store.DeleteComposeModuleByID(ctx, s, mm.FindByHandle("mod2").ID))
|
||||
|
||||
_, err = namespaceExport(t, h, ns.ID)
|
||||
h.a.Error(err)
|
||||
|
||||
cleanup(t)
|
||||
}
|
||||
60
server/tests/compose/namespace_export_simple_test.go
Normal file
60
server/tests/compose/namespace_export_simple_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
)
|
||||
|
||||
func Test_namespace_export_simple(t *testing.T) {
|
||||
ctx, h, s := setup(t)
|
||||
loadScenario(ctx, defStore, t, h)
|
||||
grantImportExport(h)
|
||||
|
||||
ns, _, _, _, _, err := fetchEntireNamespace(ctx, s, "ns1")
|
||||
h.a.NoError(err)
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "export")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "modules.export")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "charts.export")
|
||||
|
||||
arch := namespaceExportSafe(t, h, ns.ID)
|
||||
sessionID := namespaceImportInitSafe(t, h, arch)
|
||||
ns, mm, pp, cc := namespaceImportRun(ctx, s, t, h, sessionID, "imported", "imported")
|
||||
|
||||
h.a.Equal("imported", ns.Slug)
|
||||
h.a.NotEqual(0, ns.ID)
|
||||
|
||||
h.a.Len(mm, 3)
|
||||
exp := map[string]bool{"mod1": true, "mod2": true, "mod3": true}
|
||||
|
||||
for i, m := range mm {
|
||||
h.a.True(exp[m.Handle])
|
||||
if i > 0 {
|
||||
h.a.NotEqual(m.Handle, mm[i-1].Handle)
|
||||
}
|
||||
}
|
||||
|
||||
h.a.Len(pp, 3)
|
||||
exp = map[string]bool{
|
||||
"pg1": true,
|
||||
"rpg2": true,
|
||||
"pg2": true,
|
||||
}
|
||||
for i, p := range pp {
|
||||
h.a.True(exp[p.Handle])
|
||||
if i > 0 {
|
||||
h.a.NotEqual(p.Handle, mm[i-1].Handle)
|
||||
}
|
||||
}
|
||||
|
||||
parent := pp.FindByHandle("rpg2")
|
||||
child := pp.FindByHandle("pg2")
|
||||
h.a.Equal(child.SelfID, parent.ID)
|
||||
|
||||
h.a.Len(cc, 1)
|
||||
h.a.Equal("chr1", cc[0].Handle)
|
||||
|
||||
cleanup(t)
|
||||
}
|
||||
18
server/tests/compose/namespace_export_unexisting_test.go
Normal file
18
server/tests/compose/namespace_export_unexisting_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_namespace_export_unexisting(t *testing.T) {
|
||||
ctx, h, s := setup(t)
|
||||
grantImportExport(h)
|
||||
_ = ctx
|
||||
_ = s
|
||||
|
||||
_, err := namespaceExport(t, h, 42)
|
||||
h.a.Error(err)
|
||||
h.a.Contains(err.Error(), "namespace does not exist")
|
||||
|
||||
cleanup(t)
|
||||
}
|
||||
24
server/tests/compose/namespace_import_test.go
Normal file
24
server/tests/compose/namespace_import_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_namespace_import(t *testing.T) {
|
||||
t.Run("nested", func(_ *testing.T) {
|
||||
_, h, _ := setup(t)
|
||||
grantImportExport(h)
|
||||
|
||||
sessionID := namespaceImportInitPathSafe(t, h, "nested.zip")
|
||||
h.a.NotEqual(0, sessionID)
|
||||
})
|
||||
|
||||
t.Run("no namespace", func(_ *testing.T) {
|
||||
_, h, _ := setup(t)
|
||||
grantImportExport(h)
|
||||
|
||||
_, err := namespaceImportInitPath(t, h, "no-ns.zip")
|
||||
h.a.Error(err)
|
||||
h.a.Contains(err.Error(), "namespace.errors.importMissingNamespace")
|
||||
})
|
||||
}
|
||||
293
server/tests/compose/namespace_test.go
Normal file
293
server/tests/compose/namespace_test.go
Normal file
@@ -0,0 +1,293 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
labelTypes "github.com/cortezaproject/corteza/server/pkg/label/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/rand"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
jsonpath "github.com/steinfletcher/apitest-jsonpath"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func (h helper) clearNamespaces() {
|
||||
h.noError(store.TruncateComposeAttachments(context.Background(), service.DefaultStore))
|
||||
h.noError(store.TruncateComposeNamespaces(context.Background(), service.DefaultStore))
|
||||
}
|
||||
|
||||
func (h helper) makeNamespace(name string) *types.Namespace {
|
||||
ns := &types.Namespace{Name: name, Slug: name}
|
||||
ns.ID = id.Next()
|
||||
ns.CreatedAt = time.Now()
|
||||
h.noError(store.CreateComposeNamespace(context.Background(), service.DefaultStore, ns))
|
||||
return ns
|
||||
}
|
||||
|
||||
func (h helper) lookupNamespaceByID(ID uint64) *types.Namespace {
|
||||
ns, err := store.LookupComposeNamespaceByID(context.Background(), service.DefaultStore, ID)
|
||||
h.noError(err)
|
||||
return ns
|
||||
}
|
||||
|
||||
func TestNamespaceRead(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearNamespaces()
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "namespaces.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d", ns.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Equal(`$.response.name`, ns.Name)).
|
||||
Assert(jsonpath.Equal(`$.response.namespaceID`, fmt.Sprintf("%d", ns.ID))).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestNamespaceReadByHandle(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearNamespaces()
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "namespaces.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
||||
ns := h.makeNamespace("some-namespace-" + string(rand.Bytes(20)))
|
||||
|
||||
nsbh, err := service.DefaultNamespace.FindByHandle(h.secCtx(), ns.Slug)
|
||||
|
||||
h.noError(err)
|
||||
h.a.NotNil(nsbh)
|
||||
h.a.Equal(nsbh.ID, ns.ID)
|
||||
h.a.Equal(nsbh.Slug, ns.Slug)
|
||||
}
|
||||
|
||||
func TestNamespaceList(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearNamespaces()
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "namespaces.search")
|
||||
|
||||
h.makeNamespace("ns1")
|
||||
h.makeNamespace("ns2")
|
||||
|
||||
h.apiInit().
|
||||
Get("/namespace/").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestNamespaceList_filterForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearNamespaces()
|
||||
|
||||
h.makeNamespace("namespace")
|
||||
f := h.makeNamespace("namespace_forbidden")
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "namespaces.search")
|
||||
helpers.DenyMe(h, types.NamespaceRbacResource(f.ID), "read")
|
||||
|
||||
h.apiInit().
|
||||
Get("/namespace/").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.NotPresent(`$.response.set[? @.name=="namespace_forbidden"]`)).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestNamespaceCreateForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearNamespaces()
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "namespaces.search")
|
||||
|
||||
h.apiInit().
|
||||
Post("/namespace/").
|
||||
Header("Accept", "application/json").
|
||||
FormData("name", "some-namespace").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("namespace.errors.notAllowedToCreate")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestNamespaceCreate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearNamespaces()
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "namespace.create")
|
||||
|
||||
h.apiInit().
|
||||
Post("/namespace/").
|
||||
FormData("name", "some-namespace").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestNamespaceUpdateForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearNamespaces()
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d", ns.ID)).
|
||||
Header("Accept", "application/json").
|
||||
FormData("name", "changed-name").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("namespace.errors.notAllowedToUpdate")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestNamespaceUpdate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearNamespaces()
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "update")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d", ns.ID)).
|
||||
FormData("name", "changed-name").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
ns = h.lookupNamespaceByID(ns.ID)
|
||||
h.a.NotNil(ns)
|
||||
h.a.Equal("changed-name", ns.Name)
|
||||
}
|
||||
|
||||
func TestNamespaceDeleteForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearNamespaces()
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
||||
helpers.DenyMe(h, types.NamespaceRbacResource(0), "delete")
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/namespace/%d", ns.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("namespace.errors.notAllowedToDelete")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestNamespaceDelete(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearNamespaces()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "delete")
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/namespace/%d", ns.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
ns = h.lookupNamespaceByID(ns.ID)
|
||||
h.a.NotNil(ns.DeletedAt)
|
||||
}
|
||||
|
||||
func TestNamespaceLabels(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearNamespaces()
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "namespaces.search")
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "namespace.create")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "delete", "update")
|
||||
|
||||
var (
|
||||
ID uint64
|
||||
)
|
||||
|
||||
t.Run("create", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Namespace{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
"/namespace/",
|
||||
types.Namespace{Labels: map[string]labelTypes.LabelValue{"foo": {Val: "bar"}, "bar": {Val: "42"}}},
|
||||
payload,
|
||||
)
|
||||
req.NotZero(payload.ID)
|
||||
|
||||
h.a.Equal(payload.Labels["foo"].Val, "bar",
|
||||
"labels must contain foo with value bar")
|
||||
h.a.Equal(payload.Labels["bar"].Val, "42",
|
||||
"labels must contain bar with value 42")
|
||||
req.Equal(helpers.LabelsToStrings(payload.Labels), helpers.LabelsToStrings(helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID)),
|
||||
"response must match stored labels")
|
||||
|
||||
ID = payload.ID
|
||||
})
|
||||
|
||||
t.Run("update", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Namespace{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d", ID),
|
||||
&types.Namespace{Labels: map[string]labelTypes.LabelValue{"foo": {Val: "baz"}, "baz": {Val: "123"}}},
|
||||
payload,
|
||||
)
|
||||
req.NotZero(payload.ID)
|
||||
req.Nil(payload.UpdatedAt, "updatedAt must not change after changing labels")
|
||||
|
||||
req.Equal(payload.Labels["foo"].Val, "baz",
|
||||
"labels must contain foo with value baz")
|
||||
req.NotContains(payload.Labels, "bar",
|
||||
"labels must not contain bar")
|
||||
req.Equal(payload.Labels["baz"].Val, "123",
|
||||
"labels must contain baz with value 123")
|
||||
req.Equal(helpers.LabelsToStrings(payload.Labels), helpers.LabelsToStrings(helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID)),
|
||||
"response must match stored labels")
|
||||
})
|
||||
|
||||
t.Run("search", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
set = types.NamespaceSet{}
|
||||
)
|
||||
|
||||
helpers.SearchWithLabelsViaAPI(h.apiInit(), t, "/namespace/", &set, url.Values{"labels": []string{"baz=123"}})
|
||||
req.NotEmpty(set)
|
||||
req.NotNil(set.FindByID(ID))
|
||||
req.NotNil(set.FindByID(ID).Labels)
|
||||
})
|
||||
}
|
||||
34
server/tests/compose/notification_test.go
Normal file
34
server/tests/compose/notification_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
sqlxTypes "github.com/jmoiron/sqlx/types"
|
||||
"github.com/steinfletcher/apitest"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func (h helper) apiSendEmailNotification(req request.NotificationEmailSend) *apitest.Response {
|
||||
payload, err := json.Marshal(req)
|
||||
h.noError(err)
|
||||
|
||||
return h.apiInit().
|
||||
Post("/notification/email").
|
||||
JSON(string(payload)).
|
||||
Expect(h.t).
|
||||
Status(http.StatusOK)
|
||||
}
|
||||
|
||||
func TestEmailNotification(t *testing.T) {
|
||||
t.Skip("we need smtp server mock")
|
||||
h := newHelper(t)
|
||||
|
||||
h.apiSendEmailNotification(request.NotificationEmailSend{
|
||||
To: []string{"foo+to@test.tld"},
|
||||
Cc: []string{"foo+cc1@test.tld", "foo+cc2@test.tld"},
|
||||
Subject: "Subject!",
|
||||
Content: sqlxTypes.JSONText(`{}`),
|
||||
RemoteAttachments: []string{"file1", "file2"},
|
||||
}).End()
|
||||
}
|
||||
224
server/tests/compose/page_layout_test.go
Normal file
224
server/tests/compose/page_layout_test.go
Normal file
@@ -0,0 +1,224 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
jsonpath "github.com/steinfletcher/apitest-jsonpath"
|
||||
)
|
||||
|
||||
func (h helper) clearPageLayouts() {
|
||||
h.clearNamespaces()
|
||||
h.noError(store.TruncateComposePageLayouts(context.Background(), service.DefaultStore))
|
||||
}
|
||||
|
||||
func (h helper) repoMakePageLayout(ns *types.Namespace, pg *types.Page, title string) *types.PageLayout {
|
||||
res := &types.PageLayout{
|
||||
ID: id.Next(),
|
||||
CreatedAt: time.Now(),
|
||||
Meta: types.PageLayoutMeta{
|
||||
Title: title,
|
||||
},
|
||||
NamespaceID: ns.ID,
|
||||
PageID: pg.ID,
|
||||
}
|
||||
|
||||
h.noError(store.CreateComposePageLayout(context.Background(), service.DefaultStore, res))
|
||||
return res
|
||||
}
|
||||
|
||||
func (h helper) lookupPageLayoutByID(ID uint64) *types.PageLayout {
|
||||
res, err := store.LookupComposePageLayoutByID(context.Background(), service.DefaultStore, ID)
|
||||
h.noError(err)
|
||||
return res
|
||||
}
|
||||
|
||||
func TestPageLayoutRead(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPageLayouts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
||||
helpers.AllowMe(h, types.PageLayoutRbacResource(0, 0, 0), "read")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
pg := h.repoMakePage(ns, "some-page")
|
||||
ly := h.repoMakePageLayout(ns, pg, "some-page-layout")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d/page/%d/layout/%d", ns.ID, pg.ID, ly.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Equal(`$.response.meta.title`, ly.Meta.Title)).
|
||||
Assert(jsonpath.Equal(`$.response.pageLayoutID`, fmt.Sprintf("%d", ly.ID))).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPageLayoutList_filterForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPageLayouts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "pages.search")
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "read", "page-layouts.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
pg := h.repoMakePage(ns, "some-page")
|
||||
|
||||
h.repoMakePageLayout(ns, pg, "page-layout")
|
||||
ly := h.repoMakePageLayout(ns, pg, "page-layout_forbidden")
|
||||
|
||||
helpers.DenyMe(h, types.PageLayoutRbacResource(ly.NamespaceID, ly.PageID, ly.ID), "read")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d/page/%d/layout/", ns.ID, pg.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.NotPresent(`$.response.set[? @.meta.title=="page-layout_forbidden"]`)).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPageLayoutCreateForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPageLayouts()
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
pg := h.repoMakePage(ns, "some-page")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/page/%d/layout/", ns.ID, pg.ID)).
|
||||
Header("Accept", "application/json").
|
||||
JSON(fmt.Sprintf(`{
|
||||
"meta": {"title": "some-page-layout"}
|
||||
}`)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("page-layout.errors.notAllowedToCreate")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPageLayoutCreate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPageLayouts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "page-layout.create")
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
pg := h.repoMakePage(ns, "some-page")
|
||||
|
||||
rsp := struct {
|
||||
Response *types.PageLayout `json:"response"`
|
||||
}{}
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/page/%d/layout/", ns.ID, pg.ID)).
|
||||
Header("Accept", "application/json").
|
||||
JSON(fmt.Sprintf(`{
|
||||
"meta": {"title": "some-page-layout"}
|
||||
}`)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End().
|
||||
JSON(&rsp)
|
||||
|
||||
res := h.lookupPageLayoutByID(rsp.Response.ID)
|
||||
h.a.NotNil(res)
|
||||
h.a.Equal("some-page-layout", res.Meta.Title)
|
||||
}
|
||||
|
||||
func TestPageLayoutUpdateForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPageLayouts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
pg := h.repoMakePage(ns, "some-page")
|
||||
ly := h.repoMakePageLayout(ns, pg, "some-page-layout")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/page/%d/layout/%d", ns.ID, pg.ID, ly.ID)).
|
||||
Header("Accept", "application/json").
|
||||
JSON(fmt.Sprintf(`{
|
||||
"meta": {"title": "changed-name"}
|
||||
}`)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("page-layout.errors.notAllowedToUpdate")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPageLayoutUpdate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPageLayouts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
pg := h.repoMakePage(ns, "some-page")
|
||||
ly := h.repoMakePageLayout(ns, pg, "some-page-layout")
|
||||
helpers.AllowMe(h, types.PageLayoutRbacResource(0, 0, 0), "update")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/page/%d/layout/%d", ns.ID, pg.ID, ly.ID)).
|
||||
JSON(fmt.Sprintf(`{
|
||||
"meta": {"title": "changed-name"}
|
||||
}`)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
ly = h.lookupPageLayoutByID(ly.ID)
|
||||
h.a.NotNil(ly)
|
||||
h.a.Equal("changed-name", ly.Meta.Title)
|
||||
}
|
||||
|
||||
func TestPageLayoutDeleteForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPageLayouts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
||||
helpers.AllowMe(h, types.PageLayoutRbacResource(0, 0, 0), "read")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
pg := h.repoMakePage(ns, "some-page")
|
||||
ly := h.repoMakePageLayout(ns, pg, "some-page-layout")
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/namespace/%d/page/%d/layout/%d", ns.ID, pg.ID, ly.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("page-layout.errors.notAllowedToDelete")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPageLayoutDelete(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPageLayouts()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
||||
helpers.AllowMe(h, types.PageLayoutRbacResource(0, 0, 0), "read")
|
||||
helpers.AllowMe(h, types.PageLayoutRbacResource(0, 0, 0), "delete")
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
pg := h.repoMakePage(ns, "some-page")
|
||||
ly := h.repoMakePageLayout(ns, pg, "some-page-layout")
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/namespace/%d/page/%d/layout/%d", ns.ID, pg.ID, ly.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
ly = h.lookupPageLayoutByID(ly.ID)
|
||||
h.a.NotNil(ly.DeletedAt)
|
||||
}
|
||||
532
server/tests/compose/page_test.go
Normal file
532
server/tests/compose/page_test.go
Normal file
@@ -0,0 +1,532 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
labelTypes "github.com/cortezaproject/corteza/server/pkg/label/types"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
systemService "github.com/cortezaproject/corteza/server/system/service"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
jsonpath "github.com/steinfletcher/apitest-jsonpath"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func (h helper) clearPages() {
|
||||
h.clearNamespaces()
|
||||
h.noError(store.TruncateComposePages(context.Background(), service.DefaultStore))
|
||||
}
|
||||
|
||||
func (h helper) repoMakePage(ns *types.Namespace, name string) *types.Page {
|
||||
res := &types.Page{
|
||||
ID: id.Next(),
|
||||
CreatedAt: time.Now(),
|
||||
Meta: types.PageMeta{},
|
||||
Title: name,
|
||||
NamespaceID: ns.ID,
|
||||
}
|
||||
|
||||
res.Blocks = types.PageBlocks{
|
||||
{BlockID: 1},
|
||||
{BlockID: 2},
|
||||
}
|
||||
|
||||
res.Config.NavItem.Icon = &types.PageConfigIcon{
|
||||
Type: "type",
|
||||
Src: "src",
|
||||
Style: map[string]string{"sty": "le"},
|
||||
}
|
||||
|
||||
h.noError(store.CreateComposePage(context.Background(), service.DefaultStore, res))
|
||||
return res
|
||||
}
|
||||
|
||||
func (h helper) repoMakeWeightedPage(ns *types.Namespace, name string, weight int) *types.Page {
|
||||
res := &types.Page{
|
||||
ID: id.Next(),
|
||||
CreatedAt: time.Now(),
|
||||
Meta: types.PageMeta{},
|
||||
Title: name,
|
||||
NamespaceID: ns.ID,
|
||||
Weight: weight,
|
||||
}
|
||||
|
||||
h.noError(store.CreateComposePage(context.Background(), service.DefaultStore, res))
|
||||
return res
|
||||
}
|
||||
|
||||
func (h helper) lookupPageByID(ID uint64) *types.Page {
|
||||
res, err := store.LookupComposePageByID(context.Background(), service.DefaultStore, ID)
|
||||
h.noError(err)
|
||||
return res
|
||||
}
|
||||
|
||||
func TestPageRead(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPages()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "pages.search")
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "read")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
m := h.repoMakePage(ns, "some-page")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d/page/%d", ns.ID, m.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Equal(`$.response.title`, m.Title)).
|
||||
Assert(jsonpath.Equal(`$.response.pageID`, fmt.Sprintf("%d", m.ID))).
|
||||
Assert(jsonpath.Equal(`$.response.config.navItem.icon.src`, `src`)).
|
||||
Assert(jsonpath.Len(`$.response.blocks`, 2)).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPageReadByHandle(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPages()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "pages.search")
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "read")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
c := h.repoMakePage(ns, "some-page")
|
||||
|
||||
cbh, err := service.DefaultPage.FindByHandle(h.secCtx(), ns.ID, c.Handle)
|
||||
|
||||
h.noError(err)
|
||||
h.a.NotNil(cbh)
|
||||
h.a.Equal(cbh.ID, c.ID)
|
||||
h.a.Equal(cbh.Handle, c.Handle)
|
||||
}
|
||||
|
||||
func TestPageList(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPages()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "pages.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
h.repoMakePage(ns, "app")
|
||||
h.repoMakePage(ns, "app")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d/page/", ns.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPageList_filterForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPages()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "pages.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
h.repoMakePage(ns, "page")
|
||||
f := h.repoMakePage(ns, "page_forbidden")
|
||||
|
||||
helpers.DenyMe(h, types.PageRbacResource(f.NamespaceID, f.ID), "read")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d/page/", ns.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.NotPresent(`$.response.set[? @.title=="page_forbidden"]`)).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPageCreateForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPages()
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/page/", ns.ID)).
|
||||
Header("Accept", "application/json").
|
||||
FormData("title", "some-page").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("page.errors.notAllowedToCreate")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPageCreate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPages()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "pages.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "page.create")
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
|
||||
rsp := struct {
|
||||
Response *types.Page `json:"response"`
|
||||
}{}
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/page/", ns.ID)).
|
||||
Header("Accept", "application/json").
|
||||
JSON(fmt.Sprintf(`{
|
||||
"title": "some-page",
|
||||
"config":{"navItem":{"icon":{"src":"my-icon"}}},
|
||||
"blocks":[{"blockID": "1"}]
|
||||
}`)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End().
|
||||
JSON(&rsp)
|
||||
|
||||
res := h.lookupPageByID(rsp.Response.ID)
|
||||
h.a.NotNil(res)
|
||||
h.a.Equal("some-page", res.Title)
|
||||
h.a.Len(res.Blocks, 1)
|
||||
h.a.NotNil(res.Config.NavItem.Icon)
|
||||
h.a.Equal("my-icon", res.Config.NavItem.Icon.Src)
|
||||
}
|
||||
|
||||
func TestPageUpdateForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPages()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "pages.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
m := h.repoMakePage(ns, "some-page")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/page/%d", ns.ID, m.ID)).
|
||||
Header("Accept", "application/json").
|
||||
FormData("title", "changed-name").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("page.errors.notAllowedToUpdate")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPageUpdate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPages()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "pages.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
res := h.repoMakePage(ns, "some-page")
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "update")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/page/%d", ns.ID, res.ID)).
|
||||
FormData("title", "changed-name").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
res = h.lookupPageByID(res.ID)
|
||||
h.a.NotNil(res)
|
||||
h.a.Equal("changed-name", res.Title)
|
||||
}
|
||||
|
||||
func TestPageUpdateWithBlocksAndConfig(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPages()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "pages.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
res := h.repoMakePage(ns, "some-page")
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "update")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/page/%d", ns.ID, res.ID)).
|
||||
Header("Accept", "application/json").
|
||||
JSON(fmt.Sprintf(`{
|
||||
"title": "changed-name",
|
||||
"config":{"navItem":{"icon":{"src":"my-icon"}}},
|
||||
"blocks":[{"blockID": "1"}]
|
||||
}`)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
res = h.lookupPageByID(res.ID)
|
||||
h.a.NotNil(res)
|
||||
h.a.Equal("changed-name", res.Title)
|
||||
h.a.NotNil(res.Config.NavItem.Icon)
|
||||
h.a.Equal("my-icon", res.Config.NavItem.Icon.Src)
|
||||
h.a.Len(res.Blocks, 1)
|
||||
}
|
||||
|
||||
func TestPageReorder(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPages()
|
||||
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "update")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "pages.search")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
res := h.repoMakePage(ns, "some-page")
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/namespace/%d/page/%d/reorder", ns.ID, res.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPageDeleteForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPages()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "pages.search")
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "read")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
m := h.repoMakePage(ns, "some-page")
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/namespace/%d/page/%d", ns.ID, m.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("page.errors.notAllowedToDelete")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPageDelete(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPages()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "pages.search")
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "read")
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "delete")
|
||||
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
res := h.repoMakePage(ns, "some-page")
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/namespace/%d/page/%d", ns.ID, res.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
res = h.lookupPageByID(res.ID)
|
||||
h.a.NotNil(res.DeletedAt)
|
||||
}
|
||||
|
||||
func TestPageTreeRead(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPages()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "pages.search")
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "read")
|
||||
ns := h.makeNamespace("some-namespace")
|
||||
h.repoMakeWeightedPage(ns, "p1", 1)
|
||||
h.repoMakeWeightedPage(ns, "p4", 4)
|
||||
h.repoMakeWeightedPage(ns, "p3", 3)
|
||||
h.repoMakeWeightedPage(ns, "p2", 2)
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/namespace/%d/page/tree", ns.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Equal(`$.response[0].title`, "p1")).
|
||||
Assert(jsonpath.Equal(`$.response[1].title`, "p2")).
|
||||
Assert(jsonpath.Equal(`$.response[2].title`, "p3")).
|
||||
Assert(jsonpath.Equal(`$.response[3].title`, "p4")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPageAttachment(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPages()
|
||||
|
||||
ns := h.makeNamespace("page attachment testing namespace")
|
||||
page := h.repoMakePage(ns, "some-page")
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "read", "update")
|
||||
|
||||
xxlBlob := bytes.Repeat([]byte("0"), 1_000_001)
|
||||
|
||||
testImgFh, err := os.ReadFile("./testdata/test.png")
|
||||
h.noError(err)
|
||||
|
||||
defer func() {
|
||||
// reset settings after we're done
|
||||
systemService.CurrentSettings.Compose.Page.Attachments.MaxSize = 0
|
||||
systemService.CurrentSettings.Compose.Page.Attachments.Mimetypes = nil
|
||||
}()
|
||||
|
||||
// one megabyte limit
|
||||
systemService.CurrentSettings.Compose.Page.Attachments.MaxSize = 1
|
||||
systemService.CurrentSettings.Compose.Page.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{},
|
||||
helpers.AssertError("attachment.errors.notAllowedToCreateEmptyAttachment"),
|
||||
},
|
||||
{
|
||||
"no file",
|
||||
nil,
|
||||
"empty",
|
||||
"plain/text",
|
||||
map[string]string{},
|
||||
helpers.AssertError("attachment.errors.notAllowedToCreateEmptyAttachment"),
|
||||
},
|
||||
{
|
||||
"valid upload, no constraints",
|
||||
[]byte("."),
|
||||
"dot",
|
||||
"plain/text",
|
||||
map[string]string{},
|
||||
helpers.AssertNoErrors,
|
||||
},
|
||||
{
|
||||
"global max size - over sized",
|
||||
xxlBlob,
|
||||
"numbers",
|
||||
"plain/text",
|
||||
map[string]string{},
|
||||
helpers.AssertError("attachment.errors.tooLarge"),
|
||||
},
|
||||
{
|
||||
"global mimetype - invalid",
|
||||
testImgFh,
|
||||
"numbers.gif",
|
||||
"image/gif",
|
||||
map[string]string{},
|
||||
helpers.AssertError("attachment.errors.failedToProcessImage"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cc {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
h.t = t
|
||||
|
||||
helpers.InitFileUpload(t, h.apiInit(),
|
||||
fmt.Sprintf("/namespace/%d/page/%d/attachment", page.NamespaceID, page.ID),
|
||||
c.form,
|
||||
c.file,
|
||||
c.fname,
|
||||
c.mtype,
|
||||
).
|
||||
Status(http.StatusOK).
|
||||
Assert(c.test).
|
||||
End()
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPageLabels(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearPages()
|
||||
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "read", "pages.search")
|
||||
helpers.AllowMe(h, types.NamespaceRbacResource(0), "page.create")
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "read")
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "update")
|
||||
helpers.AllowMe(h, types.PageRbacResource(0, 0), "delete")
|
||||
|
||||
var (
|
||||
ns = h.makeNamespace("some-namespace")
|
||||
ID uint64
|
||||
)
|
||||
|
||||
t.Run("create", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Page{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/page/", ns.ID),
|
||||
types.Page{Labels: map[string]labelTypes.LabelValue{"foo": {Val: "bar"}, "bar": {Val: "42"}}},
|
||||
payload,
|
||||
)
|
||||
req.NotZero(payload.ID)
|
||||
|
||||
h.a.Equal(payload.Labels["foo"].Val, "bar",
|
||||
"labels must contain foo with value bar")
|
||||
h.a.Equal(payload.Labels["bar"].Val, "42",
|
||||
"labels must contain bar with value 42")
|
||||
req.Equal(helpers.LabelsToStrings(payload.Labels), helpers.LabelsToStrings(helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID)),
|
||||
"response must match stored labels")
|
||||
|
||||
ID = payload.ID
|
||||
})
|
||||
|
||||
t.Run("update", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Page{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(),
|
||||
t,
|
||||
fmt.Sprintf("/namespace/%d/page/%d", ns.ID, ID),
|
||||
types.Page{Labels: map[string]labelTypes.LabelValue{"foo": {Val: "baz"}, "baz": {Val: "123"}}},
|
||||
payload,
|
||||
)
|
||||
req.NotZero(payload.ID)
|
||||
req.Nil(payload.UpdatedAt, "updatedAt must not change after changing labels")
|
||||
|
||||
req.Equal(payload.Labels["foo"].Val, "baz",
|
||||
"labels must contain foo with value baz")
|
||||
req.NotContains(payload.Labels, "bar",
|
||||
"labels must not contain bar")
|
||||
req.Equal(payload.Labels["baz"].Val, "123",
|
||||
"labels must contain baz with value 123")
|
||||
req.Equal(helpers.LabelsToStrings(payload.Labels), helpers.LabelsToStrings(helpers.LoadLabelsFromStore(t, service.DefaultStore, payload.LabelResourceKind(), payload.ID)),
|
||||
"response must match stored labels")
|
||||
})
|
||||
|
||||
t.Run("search", func(t *testing.T) {
|
||||
if ID == 0 {
|
||||
t.Skip("label/create test not ran")
|
||||
}
|
||||
|
||||
var (
|
||||
req = require.New(t)
|
||||
set = types.PageSet{}
|
||||
)
|
||||
|
||||
helpers.SearchWithLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("/namespace/%d/page/", ns.ID),
|
||||
&set,
|
||||
url.Values{"labels": []string{"baz=123"}},
|
||||
)
|
||||
req.NotEmpty(set)
|
||||
req.NotNil(set.FindByID(ID))
|
||||
req.NotNil(set.FindByID(ID).Labels)
|
||||
})
|
||||
}
|
||||
121
server/tests/compose/permissions_test.go
Normal file
121
server/tests/compose/permissions_test.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/rbac"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
"github.com/steinfletcher/apitest-jsonpath"
|
||||
)
|
||||
|
||||
func TestPermissionsEffective(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
helpers.DenyMe(h, types.ComponentRbacResource(), "namespace.create")
|
||||
|
||||
h.apiInit().
|
||||
Get("/permissions/effective").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPermissionsList(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "grant")
|
||||
|
||||
h.apiInit().
|
||||
Debug().
|
||||
Get("/permissions/").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present(fmt.Sprintf(`$.response[? @.type=="%s"]`, types.ComponentResourceType))).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPermissionsRead(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "grant")
|
||||
helpers.DenyMe(h, types.ComponentRbacResource(), "namespace.create")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/permissions/%d/rules", h.roleID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPermissionsUpdate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "grant")
|
||||
|
||||
h.apiInit().
|
||||
Patch(fmt.Sprintf("/permissions/%d/rules", h.roleID)).
|
||||
Header("Accept", "application/json").
|
||||
JSON(fmt.Sprintf(
|
||||
`{"rules":[{"resource":"%s","operation":"namespace.create","access":"allow"},{"resource":"%s","operation":"read","access":"allow"}]}`,
|
||||
types.ComponentRbacResource(),
|
||||
"corteza::compose:chart/1/2",
|
||||
//types.ChartRbacResource(1, 0),
|
||||
)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestPermissionsDelete(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
p := rbac.Global()
|
||||
|
||||
// Make sure our user can grant
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "grant")
|
||||
|
||||
// New role.
|
||||
permDelRole := h.roleID + 1
|
||||
|
||||
h.a.Len(rbac.Global().FindRulesByRoleID(permDelRole), 0)
|
||||
|
||||
// Setup a few fake rules for new role
|
||||
helpers.Grant(rbac.AllowRule(permDelRole, types.ComponentRbacResource(), "namespace.create"))
|
||||
|
||||
h.a.Len(p.FindRulesByRoleID(permDelRole), 1)
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/permissions/%d/rules", permDelRole)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
// Make sure all rules for this role are deleted
|
||||
for _, r := range p.FindRulesByRoleID(permDelRole) {
|
||||
h.a.True(r.Access == rbac.Inherit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermissionsTrace(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "grant")
|
||||
|
||||
h.apiInit().
|
||||
Get("/permissions/trace").
|
||||
Query("roleID[]", "1").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present(`$.response`)).
|
||||
End()
|
||||
}
|
||||
150
server/tests/compose/record_access_context_test.go
Normal file
150
server/tests/compose/record_access_context_test.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
systemTypes "github.com/cortezaproject/corteza/server/system/types"
|
||||
)
|
||||
|
||||
func crissCrossUserRoles(ctx context.Context, s store.Storer, h helper, uu systemTypes.UserSet, rr systemTypes.RoleSet) (map[string]*systemTypes.User, map[string]*systemTypes.Role) {
|
||||
ux := make(map[string]*systemTypes.User)
|
||||
for _, r := range uu {
|
||||
ux[r.Name] = r
|
||||
}
|
||||
|
||||
rx := make(map[string]*systemTypes.Role)
|
||||
for _, r := range rr {
|
||||
rx[r.Handle] = r
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
if _, ok := rx[u.Name]; !ok {
|
||||
h.a.FailNow(fmt.Sprintf("corresponding role not found for user: r: %s; u: %s", u.Name, u.Handle))
|
||||
}
|
||||
rID := rx[u.Name].ID
|
||||
h.a.NoError(store.CreateRoleMember(ctx, s, &systemTypes.RoleMember{
|
||||
RoleID: rID,
|
||||
Resource: fmt.Sprintf("corteza::system:user/%d", u.ID),
|
||||
}))
|
||||
u.SetRoles(rID)
|
||||
}
|
||||
|
||||
return ux, rx
|
||||
}
|
||||
|
||||
func Test_record_access_context(t *testing.T) {
|
||||
t.Skip("skipping due to Envoy not yet refactored")
|
||||
// ctx, h, s := setup(t)
|
||||
// loadScenario(ctx, defStore, t, h)
|
||||
|
||||
// // setup
|
||||
// rr, _, err := store.SearchRoles(ctx, s, systemTypes.RoleFilter{})
|
||||
// h.a.NoError(err)
|
||||
// uu, _, err := store.SearchUsers(ctx, s, systemTypes.UserFilter{})
|
||||
// h.a.NoError(err)
|
||||
|
||||
// ux, rx := crissCrossUserRoles(ctx, s, h, uu, rr)
|
||||
|
||||
// ns, err := store.LookupComposeNamespaceBySlug(ctx, s, "ns1")
|
||||
// h.a.NoError(err)
|
||||
|
||||
// mod, err := store.LookupComposeModuleByNamespaceIDHandle(ctx, s, ns.ID, "mod1")
|
||||
// h.a.NoError(err)
|
||||
|
||||
// records, _, err := store.SearchComposeRecords(ctx, s, mod, types.RecordFilter{})
|
||||
// h.a.NoError(err)
|
||||
// rec := records[0]
|
||||
|
||||
// for _, r := range rr {
|
||||
// helpers.Allow(r, types.NamespaceRbacResource(0), "read")
|
||||
// helpers.Allow(r, types.ModuleRbacResource(0, 0), "read")
|
||||
// }
|
||||
|
||||
// helpers.DenyMe(h, rec.RbacResource(), "read")
|
||||
// helpers.Allow(rx["owner"], rec.RbacResource(), "read")
|
||||
// helpers.Allow(rx["creator"], rec.RbacResource(), "read")
|
||||
// helpers.Allow(rx["updater"], rec.RbacResource(), "read")
|
||||
// helpers.Allow(rx["deleter"], rec.RbacResource(), "read")
|
||||
|
||||
// h.a.NoError(service.UpdateRbacRoles(ctx, testApp.Log, rbac.Global(), nil, nil, nil))
|
||||
// rbac.Global().Reload(ctx)
|
||||
|
||||
// t.Run("generic user with no ctx role", func(t *testing.T) {
|
||||
// h.apiInit().
|
||||
// Get(fmt.Sprintf("/namespace/%d/module/%d/record/%d", mod.NamespaceID, mod.ID, rec.ID)).
|
||||
// Header("Accept", "application/json").
|
||||
// Expect(t).
|
||||
// Status(http.StatusOK).
|
||||
// Assert(helpers.AssertError("record.errors.notAllowedToRead")).
|
||||
// End()
|
||||
// })
|
||||
|
||||
// t.Run("user with owner ctx role", func(t *testing.T) {
|
||||
// h.identityToHelper(ux["owner"])
|
||||
// h.apiInit().
|
||||
// Get(fmt.Sprintf("/namespace/%d/module/%d/record/%d", mod.NamespaceID, mod.ID, rec.ID)).
|
||||
// Header("Accept", "application/json").
|
||||
// Expect(t).
|
||||
// Status(http.StatusOK).
|
||||
// Assert(helpers.AssertNoErrors).
|
||||
// End()
|
||||
// })
|
||||
|
||||
// t.Run("user with creator ctx role", func(t *testing.T) {
|
||||
// h.identityToHelper(ux["creator"])
|
||||
// h.apiInit().
|
||||
// Get(fmt.Sprintf("/namespace/%d/module/%d/record/%d", mod.NamespaceID, mod.ID, rec.ID)).
|
||||
// Header("Accept", "application/json").
|
||||
// Expect(t).
|
||||
// Status(http.StatusOK).
|
||||
// Assert(helpers.AssertNoErrors).
|
||||
// End()
|
||||
// })
|
||||
|
||||
// t.Run("user with updater ctx role", func(t *testing.T) {
|
||||
// h.identityToHelper(ux["updater"])
|
||||
// h.apiInit().
|
||||
// Get(fmt.Sprintf("/namespace/%d/module/%d/record/%d", mod.NamespaceID, mod.ID, rec.ID)).
|
||||
// Header("Accept", "application/json").
|
||||
// Expect(t).
|
||||
// Status(http.StatusOK).
|
||||
// Assert(helpers.AssertNoErrors).
|
||||
// End()
|
||||
// })
|
||||
|
||||
// t.Run("user with deleter ctx role", func(t *testing.T) {
|
||||
// h.identityToHelper(ux["deleter"])
|
||||
// h.apiInit().
|
||||
// Get(fmt.Sprintf("/namespace/%d/module/%d/record/%d", mod.NamespaceID, mod.ID, rec.ID)).
|
||||
// Header("Accept", "application/json").
|
||||
// Expect(t).
|
||||
// Status(http.StatusOK).
|
||||
// Assert(helpers.AssertNoErrors).
|
||||
// End()
|
||||
// })
|
||||
|
||||
// t.Run("user with creator ctx role", func(t *testing.T) {
|
||||
// h.identityToHelper(ux["creator"])
|
||||
// h.apiInit().
|
||||
// Get(fmt.Sprintf("/namespace/%d/module/%d/record/%d", mod.NamespaceID, mod.ID, rec.ID)).
|
||||
// Header("Accept", "application/json").
|
||||
// Expect(t).
|
||||
// Status(http.StatusOK).
|
||||
// Assert(helpers.AssertNoErrors).
|
||||
// End()
|
||||
// })
|
||||
|
||||
// t.Run("user with creator ctx role", func(t *testing.T) {
|
||||
// h.identityToHelper(ux["creator"])
|
||||
// h.apiInit().
|
||||
// Get(fmt.Sprintf("/namespace/%d/module/%d/record/%d", mod.NamespaceID, mod.ID, rec.ID)).
|
||||
// Header("Accept", "application/json").
|
||||
// Expect(t).
|
||||
// Status(http.StatusOK).
|
||||
// Assert(helpers.AssertNoErrors).
|
||||
// End()
|
||||
// })
|
||||
}
|
||||
139
server/tests/compose/record_batch_test.go
Normal file
139
server/tests/compose/record_batch_test.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
"github.com/steinfletcher/apitest-jsonpath"
|
||||
)
|
||||
|
||||
func TestBatchRecordCreate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearRecords()
|
||||
|
||||
ns := h.makeNamespace("batch testing namespace")
|
||||
parentModule := h.makeRecordModuleWithFieldsOnNs("record testing module", ns)
|
||||
childModule := h.makeRecordModuleWithFieldsOnNs("record testing module child", ns,
|
||||
&types.ModuleField{
|
||||
Name: "name",
|
||||
},
|
||||
&types.ModuleField{
|
||||
Name: "parent_ref",
|
||||
Kind: "Record",
|
||||
Options: types.ModuleFieldOptions{
|
||||
"moduleID": strconv.FormatUint(parentModule.ID, 10),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
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/", parentModule.NamespaceID, parentModule.ID)).
|
||||
JSON(fmt.Sprintf(`{"values": [], "records": [{"refField": "parent_ref", "set": [{"moduleID": "%d", "values": []}]}]}`, childModule.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present(`$.response.recordID`)).
|
||||
Assert(jsonpath.Len(`$.response.records`, 1)).
|
||||
Assert(jsonpath.Present(`$.response.records[0].recordID`)).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestBatchRecordUpdate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearRecords()
|
||||
|
||||
ns := h.makeNamespace("batch testing namespace")
|
||||
childModule := h.makeRecordModuleWithFieldsOnNs("record testing module child", ns)
|
||||
module := h.makeRecordModuleWithFieldsOnNs("record testing module", ns)
|
||||
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "update")
|
||||
helpers.AllowMe(h, types.ModuleFieldRbacResource(0, 0, 0), "record.value.update")
|
||||
|
||||
record := h.makeRecord(module)
|
||||
childRecord := h.makeRecord(childModule, &types.RecordValue{Name: "another_record", Value: strconv.FormatUint(record.ID, 10), Ref: record.ID})
|
||||
|
||||
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": "Some Name"}], "records": [{"refField": "another_record", "set": [{"moduleID": "%d", "recordID": "%d", "values": [{"name": "name", "value": "Another Name"}]}]}]}`, childModule.ID, childRecord.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Equal(`$.response.recordID`, strconv.FormatUint(record.ID, 10))).
|
||||
Assert(jsonpath.Len(`$.response.records`, 1)).
|
||||
Assert(jsonpath.Equal(`$.response.records[0].recordID`, strconv.FormatUint(childRecord.ID, 10))).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestBatchRecordDelete(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearRecords()
|
||||
|
||||
ns := h.makeNamespace("batch testing namespace")
|
||||
module := h.makeRecordModuleWithFieldsOnNs("record testing module", ns)
|
||||
childModule := h.makeRecordModuleWithFieldsOnNs("record testing module child", ns)
|
||||
|
||||
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "update", "delete")
|
||||
|
||||
record := h.makeRecord(module)
|
||||
childRecord := h.makeRecord(childModule, &types.RecordValue{Name: "another_record", Value: strconv.FormatUint(record.ID, 10), Ref: record.ID})
|
||||
|
||||
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": "Some Name"}], "records": [{"refField": "another_record", "set": [{"deletedAt": "2020-05-15T15:01:02+02:00", "moduleID": "%d", "recordID": "%d", "values": [{"name": "name", "value": "Another Name"}]}]}]}`, childModule.ID, childRecord.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Equal(`$.response.recordID`, strconv.FormatUint(record.ID, 10))).
|
||||
Assert(jsonpath.Len(`$.response.records`, 1)).
|
||||
Assert(jsonpath.Equal(`$.response.records[0].recordID`, strconv.FormatUint(childRecord.ID, 10))).
|
||||
End()
|
||||
|
||||
record = h.lookupRecordByID(module, childRecord.ID)
|
||||
h.a.NotNil(record.DeletedAt)
|
||||
}
|
||||
|
||||
func TestBatchRecordMixed(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearRecords()
|
||||
|
||||
ns := h.makeNamespace("batch testing namespace")
|
||||
parentModule := h.makeRecordModuleWithFieldsOnNs("record testing module", ns)
|
||||
childModule := h.makeRecordModuleWithFieldsOnNs("record testing module child", ns,
|
||||
&types.ModuleField{
|
||||
Name: "name",
|
||||
},
|
||||
&types.ModuleField{
|
||||
Name: "parent_ref",
|
||||
Kind: "Record",
|
||||
Options: types.ModuleFieldOptions{
|
||||
"moduleID": strconv.FormatUint(parentModule.ID, 10),
|
||||
},
|
||||
})
|
||||
helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "record.create")
|
||||
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "update", "delete")
|
||||
|
||||
record := h.makeRecord(parentModule)
|
||||
childRecord := h.makeRecord(childModule, &types.RecordValue{Name: "parent_ref", Value: strconv.FormatUint(record.ID, 10)})
|
||||
|
||||
h.apiInit().
|
||||
Debug().
|
||||
Post(fmt.Sprintf("/namespace/%d/module/%d/record/%d", parentModule.NamespaceID, parentModule.ID, record.ID)).
|
||||
Header("Accept", "application/json").
|
||||
JSON(fmt.Sprintf(`{"values": [{"name": "name", "value": "Some Name"}], "records": [{"refField": "parent_ref", "set": [{"moduleID": "%d", "values": [{"name": "name", "value": "Added Name"}]},{"moduleID": "%d", "recordID": "%d", "values": [{"name": "name", "value": "Another Name"}]}]}]}`, childModule.ID, childModule.ID, childRecord.ID)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Equal(`$.response.recordID`, strconv.FormatUint(record.ID, 10))).
|
||||
Assert(jsonpath.Len(`$.response.records`, 2)).
|
||||
Assert(jsonpath.Present(`$.response.records[0].recordID`)).
|
||||
Assert(jsonpath.Equal(`$.response.records[1].recordID`, strconv.FormatUint(childRecord.ID, 10))).
|
||||
End()
|
||||
}
|
||||
210
server/tests/compose/record_exec_test.go
Normal file
210
server/tests/compose/record_exec_test.go
Normal file
@@ -0,0 +1,210 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/dalutils"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
"github.com/steinfletcher/apitest"
|
||||
)
|
||||
|
||||
func (h helper) apiSendRecordExec(nsID, modID uint64, proc string, args []request.ProcedureArg) *apitest.Response {
|
||||
payload, err := json.Marshal(request.RecordExec{Args: args})
|
||||
h.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").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("unknown procedure")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestRecordExecOrganize(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearRecords()
|
||||
|
||||
//helpers.AllowMe(h, types.NamespaceRbacResource(0), "read")
|
||||
//helpers.AllowMe(h, types.ModuleRbacResource(0, 0), "read")
|
||||
helpers.AllowMe(h, types.RecordRbacResource(0, 0, 0), "read", "update")
|
||||
//helpers.AllowMe(h, types.ModuleFieldRbacResource(0, 0, 0), "record.value.read", "record.value.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.makeRecord(module,
|
||||
&types.RecordValue{Name: "position", Value: strconv.Itoa(position)},
|
||||
&types.RecordValue{Name: "handle", Value: handle},
|
||||
&types.RecordValue{Name: "category", Value: cat},
|
||||
)
|
||||
}
|
||||
|
||||
assertSort := func(expectedHandles, expectedCats string) {
|
||||
// Using record service for fetching to avoid value pre-fetching etc..
|
||||
sorting, _ := filter.NewSorting("position ASC")
|
||||
set, _, err := dalutils.ComposeRecordsList(context.Background(), defDal, module, types.RecordFilter{
|
||||
ModuleID: module.ID,
|
||||
NamespaceID: module.NamespaceID,
|
||||
Sorting: sorting,
|
||||
})
|
||||
|
||||
h.noError(err)
|
||||
h.a.NotNil(set)
|
||||
|
||||
actualHandles := ""
|
||||
actualCats := ""
|
||||
|
||||
_ = set.Walk(func(r *types.Record) error {
|
||||
//fmt.Printf("%d\t%s\t%s\t%s\n", r.ID,
|
||||
// r.Values.FilterByName("position")[0].Value,
|
||||
// r.Values.FilterByName("handle")[0].Value,
|
||||
// r.Values.FilterByName("category")[0].Value,
|
||||
//)
|
||||
|
||||
v := r.Values.FilterByName("handle")
|
||||
|
||||
if len(v) == 1 {
|
||||
actualHandles += v[0].Value
|
||||
} else {
|
||||
actualHandles += strconv.Itoa(len(v))
|
||||
}
|
||||
|
||||
actualCats += r.Values.FilterByName("category")[0].Value[3:]
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
h.a.Equal(expectedHandles, actualHandles)
|
||||
h.a.Equal(expectedCats, actualCats)
|
||||
}
|
||||
|
||||
t.Logf("seeding records")
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
t.Logf("testing initial order")
|
||||
assertSort("abcdefghi", "111222333")
|
||||
|
||||
// ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
|
||||
// ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
|
||||
|
||||
t.Logf("moving 'a' to position '6'")
|
||||
// Move a to the middle
|
||||
h.apiSendRecordExec(module.NamespaceID, module.ID, "organize", request.ProcedureArgs{
|
||||
{Name: "recordID", Value: rr["a"]},
|
||||
{Name: "positionField", Value: "position"},
|
||||
{Name: "position", Value: "6"}}).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
// abcdefghi
|
||||
// ^---v
|
||||
assertSort("bcdeafghi", "112212333")
|
||||
|
||||
// ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
|
||||
// ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
|
||||
|
||||
t.Logf("moving 'i' to position '0'")
|
||||
// Move i to the beginning
|
||||
h.apiSendRecordExec(module.NamespaceID, module.ID, "organize", request.ProcedureArgs{
|
||||
{Name: "recordID", Value: rr["i"]},
|
||||
{Name: "positionField", Value: "position"},
|
||||
{Name: "position", Value: "0"}}).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
// bcdeafghi
|
||||
// v<------^
|
||||
assertSort("ibcdeafgh", "311221233")
|
||||
|
||||
// ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
|
||||
// ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
|
||||
|
||||
t.Logf("moving 'b' to position '5'")
|
||||
// Move b to the 5th place
|
||||
h.apiSendRecordExec(module.NamespaceID, module.ID, "organize", request.ProcedureArgs{
|
||||
{Name: "recordID", Value: rr["b"]},
|
||||
{Name: "filter", Value: "category = 'CAT1'"},
|
||||
{Name: "positionField", Value: "position"},
|
||||
{Name: "position", Value: "5"}}).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
// ibcdeafgh
|
||||
// ^->v
|
||||
assertSort("icdebafgh", "312211233")
|
||||
|
||||
// ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
|
||||
// ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
|
||||
|
||||
t.Logf("moving 'b' to category CAT2")
|
||||
// This will keep order of letters but move b to category-2
|
||||
h.apiSendRecordExec(module.NamespaceID, module.ID, "organize", request.ProcedureArgs{
|
||||
{Name: "recordID", Value: rr["b"]},
|
||||
{Name: "groupField", Value: "category"},
|
||||
{Name: "group", Value: "CAT2"}}).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
// icdebfagh
|
||||
// ^
|
||||
assertSort("icdebafgh", "312221233")
|
||||
|
||||
// ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
|
||||
// ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
|
||||
|
||||
lRec := h.lookupRecordByID(module, bRec.ID)
|
||||
h.a.NotNil(lRec.Values)
|
||||
h.a.Len(lRec.Values.FilterByName("category"), 1)
|
||||
h.a.Equal("CAT2", lRec.Values.FilterByName("category")[0].Value)
|
||||
}
|
||||
1780
server/tests/compose/record_test.go
Normal file
1780
server/tests/compose/record_test.go
Normal file
File diff suppressed because it is too large
Load Diff
3
server/tests/compose/testdata/namespace_duplicate/data_model/1000_namespace.yaml
vendored
Normal file
3
server/tests/compose/testdata/namespace_duplicate/data_model/1000_namespace.yaml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
namespaces:
|
||||
ns1:
|
||||
name: ns1 name
|
||||
69
server/tests/compose/testdata/namespace_duplicate/data_model/1100_modules.yaml
vendored
Normal file
69
server/tests/compose/testdata/namespace_duplicate/data_model/1100_modules.yaml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
# Provides a complete set of things to cover regarding modules and module fields.
|
||||
# mod1: defines more complicated fields, relates to mod2
|
||||
# mod2: defines a self-ref, relates to mod3
|
||||
# mod3: just a placeholder
|
||||
|
||||
namespace: ns1
|
||||
modules:
|
||||
mod1:
|
||||
name: mod1 name
|
||||
fields:
|
||||
f1:
|
||||
label: f1 label
|
||||
kind: String
|
||||
required: true
|
||||
f2:
|
||||
label: f2 label
|
||||
kind: Select
|
||||
options:
|
||||
options:
|
||||
- f2 opt 1
|
||||
- f2 opt 2
|
||||
- f2 opt 3
|
||||
f3:
|
||||
label: f3 label
|
||||
kind: Select
|
||||
options:
|
||||
options:
|
||||
- ☆☆☆☆☆
|
||||
- ★☆☆☆☆
|
||||
f4:
|
||||
label: f4 label
|
||||
kind: Record
|
||||
options:
|
||||
labelField: f_label
|
||||
module: mod2
|
||||
queryFields:
|
||||
- f1
|
||||
|
||||
mod2:
|
||||
name: mod2 name
|
||||
fields:
|
||||
f_label:
|
||||
label: f_label record label
|
||||
f_ref_self:
|
||||
label: f_ref_self label
|
||||
kind: Record
|
||||
options:
|
||||
labelField: f_label
|
||||
module: mod2
|
||||
queryFields:
|
||||
- f1
|
||||
f1:
|
||||
label: f1 label
|
||||
kind: String
|
||||
required: true
|
||||
f2:
|
||||
label: f2 label
|
||||
kind: Record
|
||||
options:
|
||||
labelField: f_label
|
||||
module: mod3
|
||||
queryFields:
|
||||
- f1
|
||||
|
||||
mod3:
|
||||
name: mod3 name
|
||||
fields:
|
||||
f_label:
|
||||
label: f_label record label
|
||||
47
server/tests/compose/testdata/namespace_duplicate/data_model/1200_pages.yaml
vendored
Normal file
47
server/tests/compose/testdata/namespace_duplicate/data_model/1200_pages.yaml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace: ns1
|
||||
pages:
|
||||
pg1:
|
||||
title: pg1 title
|
||||
blocks:
|
||||
- title: pg1 b1
|
||||
kind: Content
|
||||
xywh: [0, 0, 1, 1]
|
||||
options:
|
||||
body: pg1 b1 content body
|
||||
|
||||
- title: pg1 b2
|
||||
kind: RecordList
|
||||
xywh: [0, 1, 1, 1]
|
||||
options:
|
||||
module: mod1
|
||||
fields:
|
||||
- name: f1
|
||||
- name: f2
|
||||
variants:
|
||||
bodyBg: white
|
||||
border: primary
|
||||
headerBg: white
|
||||
headerText: primary
|
||||
|
||||
- title: pg1 b3
|
||||
kind: Chart
|
||||
xywh: [0, 2, 1, 1]
|
||||
options:
|
||||
chart: chr1
|
||||
variants:
|
||||
bodyBg: white
|
||||
border: primary
|
||||
headerBg: white
|
||||
headerText: primary
|
||||
|
||||
rpg2:
|
||||
handle: rpg2
|
||||
module: mod1
|
||||
title: Record page for module "mod1"
|
||||
blocks:
|
||||
- title: rpg2 b1 title
|
||||
kind: Record
|
||||
options:
|
||||
fields:
|
||||
- name: f1
|
||||
- name: f2
|
||||
18
server/tests/compose/testdata/namespace_duplicate/data_model/1300_charts.yaml
vendored
Normal file
18
server/tests/compose/testdata/namespace_duplicate/data_model/1300_charts.yaml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace: ns1
|
||||
charts:
|
||||
chr1:
|
||||
name: chr1 name
|
||||
config:
|
||||
reports:
|
||||
- dimensions:
|
||||
- field: Status
|
||||
modifier: (no grouping / buckets)
|
||||
filter: ""
|
||||
metrics:
|
||||
- backgroundColor: '#11ff57'
|
||||
field: count
|
||||
fixTooltips: true
|
||||
type: pie
|
||||
module: mod1
|
||||
renderer: {}
|
||||
colorScheme: tableau.Tableau10
|
||||
3
server/tests/compose/testdata/namespace_export_automation/data_model/1000_namespace.yaml
vendored
Normal file
3
server/tests/compose/testdata/namespace_export_automation/data_model/1000_namespace.yaml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
namespaces:
|
||||
ns1:
|
||||
name: ns1 name
|
||||
17
server/tests/compose/testdata/namespace_export_automation/data_model/1100_modules.yaml
vendored
Normal file
17
server/tests/compose/testdata/namespace_export_automation/data_model/1100_modules.yaml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace: ns1
|
||||
modules:
|
||||
mod1:
|
||||
name: mod1 name
|
||||
fields:
|
||||
f1:
|
||||
label: f1 label
|
||||
kind: String
|
||||
required: true
|
||||
f2:
|
||||
label: f2 label
|
||||
kind: Select
|
||||
options:
|
||||
options:
|
||||
- f2 opt 1
|
||||
- f2 opt 2
|
||||
- f2 opt 3
|
||||
60
server/tests/compose/testdata/namespace_export_automation/data_model/1200_pages.yaml
vendored
Normal file
60
server/tests/compose/testdata/namespace_export_automation/data_model/1200_pages.yaml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
namespace: ns1
|
||||
pages:
|
||||
pg1:
|
||||
title: pg1 title
|
||||
blocks:
|
||||
- title: pg1 b1
|
||||
kind: Content
|
||||
xywh: [0, 0, 1, 1]
|
||||
options:
|
||||
body: pg1 b1 content body
|
||||
|
||||
- title: pg1 b2
|
||||
kind: RecordList
|
||||
xywh: [0, 1, 1, 1]
|
||||
options:
|
||||
module: mod1
|
||||
fields:
|
||||
- name: f1
|
||||
- name: f2
|
||||
|
||||
- title: pg1 b3 automation 1
|
||||
kind: Automation
|
||||
xywh: [0, 0, 0, 0]
|
||||
options:
|
||||
buttons:
|
||||
- label: workflow button
|
||||
resourceType: compose:record
|
||||
workflow: wf1
|
||||
stepID: 4
|
||||
|
||||
- title: pg1 b3 automation 2
|
||||
kind: Automation
|
||||
xywh: [0, 0, 0, 0]
|
||||
options:
|
||||
buttons:
|
||||
- label: script button
|
||||
script: /client-scripts/compose/test.js:default
|
||||
|
||||
- title: pg1 b3 automation 3
|
||||
kind: Automation
|
||||
xywh: [0, 0, 0, 0]
|
||||
options:
|
||||
buttons:
|
||||
- label: placeholder button
|
||||
|
||||
- title: pg1 b3 automation 4
|
||||
kind: Automation
|
||||
xywh: [0, 0, 0, 0]
|
||||
|
||||
rpg2:
|
||||
handle: rpg2
|
||||
module: mod1
|
||||
title: Record page for module "mod1"
|
||||
blocks:
|
||||
- title: rpg2 b1 title
|
||||
kind: Record
|
||||
options:
|
||||
fields:
|
||||
- name: f1
|
||||
- name: f2
|
||||
36
server/tests/compose/testdata/namespace_export_automation/data_model/1300_workflows.yaml
vendored
Normal file
36
server/tests/compose/testdata/namespace_export_automation/data_model/1300_workflows.yaml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
workflows:
|
||||
wf1:
|
||||
meta:
|
||||
name: wf1
|
||||
enabled: true
|
||||
keepSessions: 0
|
||||
triggers:
|
||||
- resourceType: compose:record
|
||||
eventType: beforeCreate
|
||||
constraints:
|
||||
- name: namespace.handle
|
||||
op: =
|
||||
values:
|
||||
- ns1
|
||||
enabled: true
|
||||
stepID: 4
|
||||
steps:
|
||||
- id: 4
|
||||
kind: prompt
|
||||
ref: alert
|
||||
arguments:
|
||||
- target: message
|
||||
source: ""
|
||||
expr: ""
|
||||
value: Test
|
||||
type: String
|
||||
tests: []
|
||||
results: []
|
||||
meta:
|
||||
name: ""
|
||||
description: ""
|
||||
visual:
|
||||
id: "4"
|
||||
parent: "1"
|
||||
value: null
|
||||
xywh: [0, 0, 0, 0]
|
||||
3
server/tests/compose/testdata/namespace_export_empty/data_model/1000_namespace.yaml
vendored
Normal file
3
server/tests/compose/testdata/namespace_export_empty/data_model/1000_namespace.yaml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
namespaces:
|
||||
ns1:
|
||||
name: ns1 name
|
||||
3
server/tests/compose/testdata/namespace_export_missing_res/data_model/1000_namespace.yaml
vendored
Normal file
3
server/tests/compose/testdata/namespace_export_missing_res/data_model/1000_namespace.yaml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
namespaces:
|
||||
ns1:
|
||||
name: ns1 name
|
||||
42
server/tests/compose/testdata/namespace_export_missing_res/data_model/1100_modules.yaml
vendored
Normal file
42
server/tests/compose/testdata/namespace_export_missing_res/data_model/1100_modules.yaml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
namespace: ns1
|
||||
modules:
|
||||
mod1:
|
||||
name: mod1 name
|
||||
fields:
|
||||
f1:
|
||||
label: f1 label
|
||||
kind: String
|
||||
required: true
|
||||
f2:
|
||||
label: f2 label
|
||||
kind: Select
|
||||
options:
|
||||
options:
|
||||
- f2 opt 1
|
||||
- f2 opt 2
|
||||
- f2 opt 3
|
||||
f3:
|
||||
label: f3 label
|
||||
kind: Record
|
||||
options:
|
||||
labelField: f_label
|
||||
module: mod2
|
||||
queryFields:
|
||||
- f1
|
||||
|
||||
mod2:
|
||||
name: mod2 name
|
||||
fields:
|
||||
f_label:
|
||||
label: f_label record label
|
||||
f1:
|
||||
label: f1 label
|
||||
kind: String
|
||||
required: true
|
||||
|
||||
mod3:
|
||||
name: mod3 name
|
||||
fields:
|
||||
f1:
|
||||
label: f1 label
|
||||
kind: String
|
||||
47
server/tests/compose/testdata/namespace_export_missing_res/data_model/1200_pages.yaml
vendored
Normal file
47
server/tests/compose/testdata/namespace_export_missing_res/data_model/1200_pages.yaml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace: ns1
|
||||
pages:
|
||||
pg1:
|
||||
title: pg1 title
|
||||
blocks:
|
||||
- title: pg1 b1
|
||||
kind: Content
|
||||
xywh: [0, 0, 1, 1]
|
||||
options:
|
||||
body: pg1 b1 content body
|
||||
|
||||
- title: pg1 b2
|
||||
kind: RecordList
|
||||
xywh: [0, 1, 1, 1]
|
||||
options:
|
||||
module: mod1
|
||||
fields:
|
||||
- name: f1
|
||||
- name: f2
|
||||
|
||||
- title: pg1 b3
|
||||
kind: Chart
|
||||
xywh: [0, 2, 1, 1]
|
||||
options:
|
||||
chart: chr1
|
||||
|
||||
rpg2:
|
||||
handle: rpg2
|
||||
module: mod1
|
||||
title: Record page for module "mod1"
|
||||
blocks:
|
||||
- title: rpg2 b1 title
|
||||
kind: Record
|
||||
options:
|
||||
fields:
|
||||
- name: f1
|
||||
- name: f2
|
||||
|
||||
pages:
|
||||
pg2:
|
||||
title: pg2 title
|
||||
blocks:
|
||||
- title: pg2 b1
|
||||
kind: Content
|
||||
xywh: [0, 0, 1, 1]
|
||||
options:
|
||||
body: pg2 b1 content body
|
||||
18
server/tests/compose/testdata/namespace_export_missing_res/data_model/1300_charts.yaml
vendored
Normal file
18
server/tests/compose/testdata/namespace_export_missing_res/data_model/1300_charts.yaml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace: ns1
|
||||
charts:
|
||||
chr1:
|
||||
name: chr1 name
|
||||
config:
|
||||
reports:
|
||||
- dimensions:
|
||||
- field: Status
|
||||
modifier: (no grouping / buckets)
|
||||
filter: ""
|
||||
metrics:
|
||||
- backgroundColor: '#11ff57'
|
||||
field: count
|
||||
fixTooltips: true
|
||||
type: pie
|
||||
module: mod1
|
||||
renderer: {}
|
||||
colorScheme: tableau.Tableau10
|
||||
3
server/tests/compose/testdata/namespace_export_simple/data_model/1000_namespace.yaml
vendored
Normal file
3
server/tests/compose/testdata/namespace_export_simple/data_model/1000_namespace.yaml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
namespaces:
|
||||
ns1:
|
||||
name: ns1 name
|
||||
42
server/tests/compose/testdata/namespace_export_simple/data_model/1100_modules.yaml
vendored
Normal file
42
server/tests/compose/testdata/namespace_export_simple/data_model/1100_modules.yaml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
namespace: ns1
|
||||
modules:
|
||||
mod1:
|
||||
name: mod1 name
|
||||
fields:
|
||||
f1:
|
||||
label: f1 label
|
||||
kind: String
|
||||
required: true
|
||||
f2:
|
||||
label: f2 label
|
||||
kind: Select
|
||||
options:
|
||||
options:
|
||||
- f2 opt 1
|
||||
- f2 opt 2
|
||||
- f2 opt 3
|
||||
f3:
|
||||
label: f3 label
|
||||
kind: Record
|
||||
options:
|
||||
labelField: f_label
|
||||
module: mod2
|
||||
queryFields:
|
||||
- f1
|
||||
|
||||
mod2:
|
||||
name: mod2 name
|
||||
fields:
|
||||
f_label:
|
||||
label: f_label record label
|
||||
f1:
|
||||
label: f1 label
|
||||
kind: String
|
||||
required: true
|
||||
|
||||
mod3:
|
||||
name: mod3 name
|
||||
fields:
|
||||
f1:
|
||||
label: f1 label
|
||||
kind: String
|
||||
47
server/tests/compose/testdata/namespace_export_simple/data_model/1200_pages.yaml
vendored
Normal file
47
server/tests/compose/testdata/namespace_export_simple/data_model/1200_pages.yaml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace: ns1
|
||||
pages:
|
||||
pg1:
|
||||
title: pg1 title
|
||||
blocks:
|
||||
- title: pg1 b1
|
||||
kind: Content
|
||||
xywh: [0, 0, 1, 1]
|
||||
options:
|
||||
body: pg1 b1 content body
|
||||
|
||||
- title: pg1 b2
|
||||
kind: RecordList
|
||||
xywh: [0, 1, 1, 1]
|
||||
options:
|
||||
module: mod1
|
||||
fields:
|
||||
- name: f1
|
||||
- name: f2
|
||||
|
||||
- title: pg1 b3
|
||||
kind: Chart
|
||||
xywh: [0, 2, 1, 1]
|
||||
options:
|
||||
chart: chr1
|
||||
|
||||
rpg2:
|
||||
handle: rpg2
|
||||
module: mod1
|
||||
title: Record page for module "mod1"
|
||||
blocks:
|
||||
- title: rpg2 b1 title
|
||||
kind: Record
|
||||
options:
|
||||
fields:
|
||||
- name: f1
|
||||
- name: f2
|
||||
|
||||
pages:
|
||||
pg2:
|
||||
title: pg2 title
|
||||
blocks:
|
||||
- title: pg2 b1
|
||||
kind: Content
|
||||
xywh: [0, 0, 1, 1]
|
||||
options:
|
||||
body: pg2 b1 content body
|
||||
18
server/tests/compose/testdata/namespace_export_simple/data_model/1300_charts.yaml
vendored
Normal file
18
server/tests/compose/testdata/namespace_export_simple/data_model/1300_charts.yaml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace: ns1
|
||||
charts:
|
||||
chr1:
|
||||
name: chr1 name
|
||||
config:
|
||||
reports:
|
||||
- dimensions:
|
||||
- field: Status
|
||||
modifier: (no grouping / buckets)
|
||||
filter: ""
|
||||
metrics:
|
||||
- backgroundColor: '#11ff57'
|
||||
field: count
|
||||
fixTooltips: true
|
||||
type: pie
|
||||
module: mod1
|
||||
renderer: {}
|
||||
colorScheme: tableau.Tableau10
|
||||
BIN
server/tests/compose/testdata/namespace_import/nested.zip
vendored
Normal file
BIN
server/tests/compose/testdata/namespace_import/nested.zip
vendored
Normal file
Binary file not shown.
BIN
server/tests/compose/testdata/namespace_import/no-ns.zip
vendored
Normal file
BIN
server/tests/compose/testdata/namespace_import/no-ns.zip
vendored
Normal file
Binary file not shown.
30
server/tests/compose/testdata/record_access_context/data_model/compose.yaml
vendored
Normal file
30
server/tests/compose/testdata/record_access_context/data_model/compose.yaml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
namespaces:
|
||||
ns1:
|
||||
name: ns1 name
|
||||
|
||||
modules:
|
||||
mod1:
|
||||
name: mod1 name
|
||||
fields:
|
||||
f1:
|
||||
label: f1 label
|
||||
kind: String
|
||||
required: true
|
||||
f2:
|
||||
label: f2 label
|
||||
kind: Select
|
||||
options:
|
||||
options:
|
||||
- f2 opt 1
|
||||
- f2 opt 2
|
||||
- f2 opt 3
|
||||
|
||||
records:
|
||||
mod1:
|
||||
- createdBy: user_createdby
|
||||
updatedBy: user_updatedby
|
||||
deletedBy: user_deletedby
|
||||
ownedBy: user_ownedby
|
||||
values:
|
||||
f1: "mod1 f1 v1"
|
||||
f2: "f2 opt 1"
|
||||
49
server/tests/compose/testdata/record_access_context/data_model/system.yaml
vendored
Normal file
49
server/tests/compose/testdata/record_access_context/data_model/system.yaml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
users:
|
||||
user_createdby:
|
||||
email: user_createdby@test.tld
|
||||
name: creator
|
||||
|
||||
user_updatedby:
|
||||
email: user_updatedby@test.tld
|
||||
name: updater
|
||||
|
||||
user_deletedby:
|
||||
email: user_deletedby@test.tld
|
||||
name: deleter
|
||||
|
||||
user_ownedby:
|
||||
email: user_ownedby@test.tld
|
||||
name: owner
|
||||
|
||||
roles:
|
||||
owner:
|
||||
name: Owner
|
||||
meta:
|
||||
context:
|
||||
expr: resource.ownedBy == userID
|
||||
resourceType:
|
||||
- corteza::compose:record
|
||||
|
||||
creator:
|
||||
name: Creator
|
||||
meta:
|
||||
context:
|
||||
expr: resource.createdBy == userID
|
||||
resourceType:
|
||||
- corteza::compose:record
|
||||
|
||||
updater:
|
||||
name: Updater
|
||||
meta:
|
||||
context:
|
||||
expr: resource.updatedBy == userID
|
||||
resourceType:
|
||||
- corteza::compose:record
|
||||
|
||||
deleter:
|
||||
name: Deleter
|
||||
meta:
|
||||
context:
|
||||
expr: resource.deletedBy == userID
|
||||
resourceType:
|
||||
- corteza::compose:record
|
||||
6
server/tests/compose/testdata/test.csv
vendored
Normal file
6
server/tests/compose/testdata/test.csv
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
John,Doe,120 jefferson st.,Riverside, NJ, 08075
|
||||
Jack,McGinnis,220 hobo Av.,Phila, PA,09119
|
||||
"John ""Da Man""",Repici,120 Jefferson St.,Riverside, NJ,08075
|
||||
Stephen,Tyler,"7452 Terrace ""At the Plaza"" road",SomeTown,SD, 91234
|
||||
,Blankman,,SomeTown, SD, 00298
|
||||
"Joan ""the bone"", Anne",Jet,"9th, at Terrace plc",Desert City,CO,00123
|
||||
|
BIN
server/tests/compose/testdata/test.png
vendored
Normal file
BIN
server/tests/compose/testdata/test.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
Reference in New Issue
Block a user