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:
2
server/tests/.gitignore
vendored
Normal file
2
server/tests/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.env
|
||||
workflows/var
|
||||
244
server/tests/apigw/main_test.go
Normal file
244
server/tests/apigw/main_test.go
Normal file
@@ -0,0 +1,244 @@
|
||||
package apigw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/apigw/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/dal"
|
||||
"github.com/cortezaproject/corteza/server/store/adapters/rdbms/drivers/sqlite"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/app"
|
||||
as "github.com/cortezaproject/corteza/server/automation/service"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api/server"
|
||||
"github.com/cortezaproject/corteza/server/pkg/apigw"
|
||||
"github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/server/pkg/cli"
|
||||
"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"
|
||||
es "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/label"
|
||||
ltype "github.com/cortezaproject/corteza/server/pkg/label/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/cortezaproject/corteza/server/system/rest"
|
||||
"github.com/cortezaproject/corteza/server/system/service"
|
||||
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/steinfletcher/apitest"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type (
|
||||
helper struct {
|
||||
t *testing.T
|
||||
a *require.Assertions
|
||||
|
||||
cUser *sysTypes.User
|
||||
roleID uint64
|
||||
token []byte
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
testApp *app.CortezaApp
|
||||
r chi.Router
|
||||
|
||||
eventBus = eventbus.New()
|
||||
)
|
||||
|
||||
func init() {
|
||||
helpers.RecursiveDotEnvLoad()
|
||||
}
|
||||
|
||||
func InitTestApp() {
|
||||
ctx := cli.Context()
|
||||
|
||||
if testApp == nil {
|
||||
testApp = helpers.NewIntegrationTestApp(ctx, func(app *app.CortezaApp) (err error) {
|
||||
service.DefaultStore, err = sqlite.ConnectInMemory(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
eventbus.Set(eventBus)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := testApp.Activate(ctx); err != nil {
|
||||
panic(fmt.Errorf("could not activate corteza: %v", err))
|
||||
}
|
||||
|
||||
if r == nil {
|
||||
r = chi.NewRouter()
|
||||
r.Use(server.BaseMiddleware(false, logger.Default())...)
|
||||
helpers.BindAuthMiddleware(r)
|
||||
|
||||
// Sys routes for route management tests
|
||||
r.Group(rest.MountRoutes())
|
||||
|
||||
// API gw routes
|
||||
apigw.Setup(types.Config{Enabled: true, Profiler: struct {
|
||||
Enabled bool
|
||||
Global bool
|
||||
}{Enabled: true, Global: true}}, service.DefaultLogger, service.DefaultStore)
|
||||
err := apigw.Service().Reload(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
r.Handle("/*", apigw.Service())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
InitTestApp()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func newHelper(t *testing.T) helper {
|
||||
h := helper{
|
||||
t: t,
|
||||
a: require.New(t),
|
||||
roleID: id.Next(),
|
||||
cUser: &sysTypes.User{
|
||||
ID: id.Next(),
|
||||
},
|
||||
}
|
||||
|
||||
h.cUser.SetRoles(h.roleID)
|
||||
helpers.UpdateRBAC(h.roleID)
|
||||
|
||||
var err error
|
||||
ctx := context.Background()
|
||||
h.token, err = auth.TokenIssuer.Issue(ctx, auth.WithIdentity(h.cUser))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
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 setupScenario(t *testing.T) (context.Context, helper, store.Storer) {
|
||||
ctx, h, s := setup(t)
|
||||
loadScenario(ctx, s, t, h)
|
||||
loadRbacRules(ctx, s, t, h)
|
||||
_ = apigw.Service().Reload(ctx)
|
||||
|
||||
return ctx, h, s
|
||||
}
|
||||
|
||||
func loadRbacRules(ctx context.Context, s store.Storer, t *testing.T, h helper) {
|
||||
helpers.AllowMeWorkflowSearch(h)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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 (h helper) setLabel(res label.LabeledResource, name, value string) {
|
||||
h.a.NoError(store.UpsertLabel(h.secCtx(), service.DefaultStore, <ype.Label{
|
||||
Kind: res.LabelResourceKind(),
|
||||
ResourceID: res.LabelResourceID(),
|
||||
Name: name,
|
||||
Value: ltype.LabelValue{Val: value},
|
||||
}))
|
||||
}
|
||||
|
||||
func loadScenario(ctx context.Context, s store.Storer, t *testing.T, h helper) {
|
||||
loadScenarioWithName(ctx, s, t, h, t.Name()[5:])
|
||||
}
|
||||
|
||||
func loadScenarioWithName(ctx context.Context, s store.Storer, t *testing.T, h helper, scenario string) {
|
||||
cleanup(ctx, h, s)
|
||||
parseEnvoy(ctx, s, h, path.Join("testdata", scenario))
|
||||
loadWorkflows(ctx, h)
|
||||
}
|
||||
|
||||
func loadWorkflows(ctx context.Context, h helper) {
|
||||
err := as.DefaultWorkflow.Load(ctx)
|
||||
h.a.NoError(err)
|
||||
}
|
||||
|
||||
func cleanup(ctx context.Context, h helper, s store.Storer) {
|
||||
h.noError(s.TruncateApigwFilters(ctx))
|
||||
h.noError(s.TruncateApigwRoutes(ctx))
|
||||
h.noError(s.TruncateRbacRules(ctx))
|
||||
h.noError(s.TruncateAutomationTriggers(ctx))
|
||||
h.noError(s.TruncateAutomationWorkflows(ctx))
|
||||
}
|
||||
|
||||
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 := es.NewStoreEncoder(s, dal.Service(), nil)
|
||||
bld := envoy.NewBuilder(se)
|
||||
g, err := bld.Build(ctx, nn...)
|
||||
h.a.NoError(err)
|
||||
err = envoy.Encode(ctx, g, se)
|
||||
h.a.NoError(err)
|
||||
}
|
||||
20
server/tests/apigw/postfilter_string_json_test.go
Normal file
20
server/tests/apigw/postfilter_string_json_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package apigw
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_postfilter_string_json(t *testing.T) {
|
||||
var (
|
||||
_, h, _ = setupScenario(t)
|
||||
)
|
||||
|
||||
h.apiInit().
|
||||
Get("/json/string").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Body("{\"baz\":{\"@value\":{\"1\":{\"@value\":1,\"@type\":\"Float\"},\"2\":{\"@value\":2,\"@type\":\"Float\"}},\"@type\":\"Vars\"},\"foo\":{\"@value\":\"bar\",\"@type\":\"String\"}}").
|
||||
End()
|
||||
}
|
||||
20
server/tests/apigw/postfilter_string_kv_test.go
Normal file
20
server/tests/apigw/postfilter_string_kv_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package apigw
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_postfilter_string_kv(t *testing.T) {
|
||||
var (
|
||||
_, h, _ = setupScenario(t)
|
||||
)
|
||||
|
||||
h.apiInit().
|
||||
Get("/json/kv").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Body("{\"baz\":\"123\",\"foo\":\"bar\"}").
|
||||
End()
|
||||
}
|
||||
28
server/tests/apigw/prefilter_header_failing_test.go
Normal file
28
server/tests/apigw/prefilter_header_failing_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package apigw
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_prefilter_header_failing(t *testing.T) {
|
||||
var (
|
||||
_, h, _ = setupScenario(t)
|
||||
)
|
||||
|
||||
h.apiInit().
|
||||
Get("/test").
|
||||
Header("Accept", "application/json").
|
||||
Header("Token", "brute-force-guess").
|
||||
Expect(t).
|
||||
Status(http.StatusBadRequest).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
Get("/test-hyphen").
|
||||
Header("Accept-Language", "fr-CH, fr;q=0.9").
|
||||
Header("Token", "brute-force-guess").
|
||||
Expect(t).
|
||||
Status(http.StatusBadRequest).
|
||||
End()
|
||||
}
|
||||
46
server/tests/apigw/prefilter_header_n_routes_test.go
Normal file
46
server/tests/apigw/prefilter_header_n_routes_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package apigw
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_prefilter_header_n_routes(t *testing.T) {
|
||||
var (
|
||||
_, h, _ = setupScenario(t)
|
||||
)
|
||||
|
||||
// First (a) route query validation
|
||||
h.apiInit().
|
||||
Get("/a").
|
||||
Header("P", "a").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
Get("/a").
|
||||
Header("P", "b").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusBadRequest).
|
||||
End()
|
||||
|
||||
// Second (b) route query validation
|
||||
h.apiInit().
|
||||
Get("/b").
|
||||
Header("P", "b").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
Get("/b").
|
||||
Header("P", "a").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusBadRequest).
|
||||
End()
|
||||
}
|
||||
28
server/tests/apigw/prefilter_header_passing_test.go
Normal file
28
server/tests/apigw/prefilter_header_passing_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package apigw
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_prefilter_header_passing(t *testing.T) {
|
||||
var (
|
||||
_, h, _ = setupScenario(t)
|
||||
)
|
||||
|
||||
h.apiInit().
|
||||
Get("/test").
|
||||
Header("Accept", "application/json").
|
||||
Header("Token", "super-secret-token").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
Get("/test-hyphen").
|
||||
Header("Accept-Language", "fr-CH, fr;q=0.9").
|
||||
Header("Token", "super-secret-token").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
End()
|
||||
}
|
||||
28
server/tests/apigw/prefilter_query_failing_test.go
Normal file
28
server/tests/apigw/prefilter_query_failing_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package apigw
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_prefilter_query_failing(t *testing.T) {
|
||||
var (
|
||||
_, h, _ = setupScenario(t)
|
||||
)
|
||||
|
||||
h.apiInit().
|
||||
Get("/test").
|
||||
Query("token", "brute-force-guess").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusBadRequest).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
Get("/test-hyphen").
|
||||
Query("foo-bar", "encrypted-string").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusBadRequest).
|
||||
End()
|
||||
}
|
||||
46
server/tests/apigw/prefilter_query_n_routes_test.go
Normal file
46
server/tests/apigw/prefilter_query_n_routes_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package apigw
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_prefilter_query_n_routes(t *testing.T) {
|
||||
var (
|
||||
_, h, _ = setupScenario(t)
|
||||
)
|
||||
|
||||
// First (a) route query validation
|
||||
h.apiInit().
|
||||
Get("/a").
|
||||
Query("p", "a").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
Get("/a").
|
||||
Query("p", "b").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusBadRequest).
|
||||
End()
|
||||
|
||||
// Second (b) route query validation
|
||||
h.apiInit().
|
||||
Get("/b").
|
||||
Query("p", "b").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
Get("/b").
|
||||
Query("p", "a").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusBadRequest).
|
||||
End()
|
||||
}
|
||||
28
server/tests/apigw/prefilter_query_passing_test.go
Normal file
28
server/tests/apigw/prefilter_query_passing_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package apigw
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_prefilter_query_passing(t *testing.T) {
|
||||
var (
|
||||
_, h, _ = setupScenario(t)
|
||||
)
|
||||
|
||||
h.apiInit().
|
||||
Get("/test").
|
||||
Query("token", "super-secret-token").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
Get("/test-hyphen").
|
||||
Query("foo-bar", "encrypted-string").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
End()
|
||||
}
|
||||
21
server/tests/apigw/processing_payload_simple_test.go
Normal file
21
server/tests/apigw/processing_payload_simple_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package apigw
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_processor_payload_simple(t *testing.T) {
|
||||
var (
|
||||
_, h, _ = setupScenario(t)
|
||||
)
|
||||
|
||||
h.apiInit().
|
||||
Get("/test").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Body("60").
|
||||
End()
|
||||
|
||||
}
|
||||
65
server/tests/apigw/route_reload_test.go
Normal file
65
server/tests/apigw/route_reload_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package apigw
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
)
|
||||
|
||||
func Test_route_reload(t *testing.T) {
|
||||
var (
|
||||
ctx, h, s = setupScenario(t)
|
||||
)
|
||||
|
||||
r, err := store.LookupApigwRouteByEndpoint(ctx, s, "/test")
|
||||
h.a.NoError(err)
|
||||
|
||||
h.apiInit().
|
||||
Get("/test").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Body("60").
|
||||
End()
|
||||
|
||||
helpers.AllowMe(h, r.RbacResource(), "update")
|
||||
h.apiInit().
|
||||
Put(fmt.Sprintf("/apigw/route/%d", r.ID)).
|
||||
Header("Accept", "application/json").
|
||||
FormData("endpoint", "/test").
|
||||
FormData("method", "GET").
|
||||
FormData("enabled", "false").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
Get("/test").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusNotFound).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
Put(fmt.Sprintf("/apigw/route/%d", r.ID)).
|
||||
Header("Accept", "application/json").
|
||||
FormData("endpoint", "/test").
|
||||
FormData("method", "GET").
|
||||
FormData("enabled", "true").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
Get("/test").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Body("60").
|
||||
End()
|
||||
}
|
||||
38
server/tests/apigw/testdata/postfilter_string_json/def.yaml
vendored
Normal file
38
server/tests/apigw/testdata/postfilter_string_json/def.yaml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
apigateway:
|
||||
- endpoint: /json/string
|
||||
method: GET
|
||||
enabled: true
|
||||
filters:
|
||||
- ref: response
|
||||
kind: postfilter
|
||||
enabled: true
|
||||
params:
|
||||
input:
|
||||
expr: toJSON(test)
|
||||
type: String
|
||||
header:
|
||||
Content-Type: [ application/json ]
|
||||
- ref: workflow
|
||||
kind: processer
|
||||
enabled: true
|
||||
params:
|
||||
workflow: 'test_variable_json'
|
||||
|
||||
workflows:
|
||||
- id: 1
|
||||
enabled: true
|
||||
trace: false
|
||||
handle: test_variable_json
|
||||
meta:
|
||||
name: Test variable
|
||||
triggers:
|
||||
- enabled: true
|
||||
stepID: 1
|
||||
steps:
|
||||
- stepID: 1
|
||||
kind: expressions
|
||||
arguments:
|
||||
- target: test
|
||||
type: Any
|
||||
expr: |
|
||||
{"foo":"bar","baz":{"1":1, "2":2}}
|
||||
38
server/tests/apigw/testdata/postfilter_string_kv/def.yaml
vendored
Normal file
38
server/tests/apigw/testdata/postfilter_string_kv/def.yaml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
apigateway:
|
||||
- endpoint: /json/kv
|
||||
method: GET
|
||||
enabled: true
|
||||
filters:
|
||||
- ref: response
|
||||
kind: postfilter
|
||||
enabled: true
|
||||
params:
|
||||
input:
|
||||
expr: test
|
||||
type: KV
|
||||
header:
|
||||
Content-Type: [ application/json ]
|
||||
- ref: workflow
|
||||
kind: processer
|
||||
enabled: true
|
||||
params:
|
||||
workflow: 'test_variable_kv'
|
||||
|
||||
workflows:
|
||||
- id: 1
|
||||
enabled: true
|
||||
trace: false
|
||||
handle: test_variable_kv
|
||||
meta:
|
||||
name: Test variable
|
||||
triggers:
|
||||
- enabled: true
|
||||
stepID: 1
|
||||
steps:
|
||||
- stepID: 1
|
||||
kind: expressions
|
||||
arguments:
|
||||
- target: test
|
||||
type: KV
|
||||
expr: |
|
||||
{"foo":"bar","baz":123}
|
||||
20
server/tests/apigw/testdata/prefilter_header_failing/def.yaml
vendored
Normal file
20
server/tests/apigw/testdata/prefilter_header_failing/def.yaml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
apigateway:
|
||||
- endpoint: /test
|
||||
method: GET
|
||||
enabled: true
|
||||
filters:
|
||||
- ref: "header"
|
||||
kind: "prefilter"
|
||||
enabled: true
|
||||
params:
|
||||
expr: "Token == \"super-secret-token\""
|
||||
|
||||
- endpoint: /test-hyphen
|
||||
method: GET
|
||||
enabled: true
|
||||
filters:
|
||||
- ref: "header"
|
||||
kind: "prefilter"
|
||||
enabled: true
|
||||
params:
|
||||
expr: "Accept-Language == \"fr-CH, fr;q=0.9\""
|
||||
20
server/tests/apigw/testdata/prefilter_header_n_routes/def.yaml
vendored
Normal file
20
server/tests/apigw/testdata/prefilter_header_n_routes/def.yaml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
apigateway:
|
||||
- endpoint: /a
|
||||
method: GET
|
||||
enabled: true
|
||||
filters:
|
||||
- ref: "header"
|
||||
kind: "prefilter"
|
||||
enabled: true
|
||||
params:
|
||||
expr: "P == \"a\""
|
||||
|
||||
- endpoint: /b
|
||||
method: GET
|
||||
enabled: true
|
||||
filters:
|
||||
- ref: "header"
|
||||
kind: "prefilter"
|
||||
enabled: true
|
||||
params:
|
||||
expr: "P == \"b\""
|
||||
20
server/tests/apigw/testdata/prefilter_header_passing/def.yaml
vendored
Normal file
20
server/tests/apigw/testdata/prefilter_header_passing/def.yaml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
apigateway:
|
||||
- endpoint: /test
|
||||
method: GET
|
||||
enabled: true
|
||||
filters:
|
||||
- ref: "header"
|
||||
kind: "prefilter"
|
||||
enabled: true
|
||||
params:
|
||||
expr: "Token == \"super-secret-token\""
|
||||
|
||||
- endpoint: /test-hyphen
|
||||
method: GET
|
||||
enabled: true
|
||||
filters:
|
||||
- ref: "header"
|
||||
kind: "prefilter"
|
||||
enabled: true
|
||||
params:
|
||||
expr: "headers[\"Accept-Language\"] == \"fr-CH, fr;q=0.9\""
|
||||
21
server/tests/apigw/testdata/prefilter_query_failing/def.yaml
vendored
Normal file
21
server/tests/apigw/testdata/prefilter_query_failing/def.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
apigateway:
|
||||
- endpoint: /test
|
||||
method: GET
|
||||
enabled: true
|
||||
filters:
|
||||
- ref: "queryParam"
|
||||
kind: "prefilter"
|
||||
enabled: true
|
||||
params:
|
||||
expr: "token == \"super-secret-token\""
|
||||
|
||||
- endpoint: /test-hyphen
|
||||
method: GET
|
||||
enabled: true
|
||||
filters:
|
||||
- ref: "queryParam"
|
||||
kind: "prefilter"
|
||||
enabled: true
|
||||
params:
|
||||
expr: "foo-bar == \"encrypted-string\""
|
||||
|
||||
20
server/tests/apigw/testdata/prefilter_query_n_routes/def.yaml
vendored
Normal file
20
server/tests/apigw/testdata/prefilter_query_n_routes/def.yaml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
apigateway:
|
||||
- endpoint: /a
|
||||
method: GET
|
||||
enabled: true
|
||||
filters:
|
||||
- ref: "queryParam"
|
||||
kind: "prefilter"
|
||||
enabled: true
|
||||
params:
|
||||
expr: "p == \"a\""
|
||||
|
||||
- endpoint: /b
|
||||
method: GET
|
||||
enabled: true
|
||||
filters:
|
||||
- ref: "queryParam"
|
||||
kind: "prefilter"
|
||||
enabled: true
|
||||
params:
|
||||
expr: "p == \"b\""
|
||||
20
server/tests/apigw/testdata/prefilter_query_passing/def.yaml
vendored
Normal file
20
server/tests/apigw/testdata/prefilter_query_passing/def.yaml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
apigateway:
|
||||
- endpoint: /test
|
||||
method: GET
|
||||
enabled: true
|
||||
filters:
|
||||
- ref: "queryParam"
|
||||
kind: "prefilter"
|
||||
enabled: true
|
||||
params:
|
||||
expr: "token == \"super-secret-token\""
|
||||
|
||||
- endpoint: /test-hyphen
|
||||
method: GET
|
||||
enabled: true
|
||||
filters:
|
||||
- ref: "queryParam"
|
||||
kind: "prefilter"
|
||||
enabled: true
|
||||
params:
|
||||
expr: "params[\"foo-bar\"] == \"encrypted-string\""
|
||||
15
server/tests/apigw/testdata/processor_payload_simple/def.yaml
vendored
Normal file
15
server/tests/apigw/testdata/processor_payload_simple/def.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
apigateway:
|
||||
- endpoint: /test
|
||||
method: GET
|
||||
enabled: true
|
||||
filters:
|
||||
- ref: "payload"
|
||||
kind: "processer"
|
||||
enabled: true
|
||||
params:
|
||||
jsfunc: |
|
||||
const x = 10;
|
||||
const y = 20;
|
||||
const z = x + y;
|
||||
|
||||
return x + y + z
|
||||
15
server/tests/apigw/testdata/route_reload/def.yaml
vendored
Normal file
15
server/tests/apigw/testdata/route_reload/def.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
apigateway:
|
||||
- endpoint: /test
|
||||
method: GET
|
||||
enabled: true
|
||||
filters:
|
||||
- ref: "payload"
|
||||
kind: "processer"
|
||||
enabled: true
|
||||
params:
|
||||
jsfunc: |
|
||||
const x = 10;
|
||||
const y = 20;
|
||||
const z = x + y;
|
||||
|
||||
return x + y + z
|
||||
155
server/tests/automation/main_test.go
Normal file
155
server/tests/automation/main_test.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package automation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/app"
|
||||
"github.com/cortezaproject/corteza/server/automation/rest"
|
||||
"github.com/cortezaproject/corteza/server/automation/service"
|
||||
"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/eventbus"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
"github.com/cortezaproject/corteza/server/pkg/label"
|
||||
ltype "github.com/cortezaproject/corteza/server/pkg/label/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/locale"
|
||||
"github.com/cortezaproject/corteza/server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza/server/pkg/rand"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/cortezaproject/corteza/server/store/adapters/rdbms/drivers/sqlite"
|
||||
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/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
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
testApp *app.CortezaApp
|
||||
r chi.Router
|
||||
|
||||
eventBus = eventbus.New()
|
||||
)
|
||||
|
||||
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, err = sqlite.ConnectInMemory(ctx)
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
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 {
|
||||
h := helper{
|
||||
t: t,
|
||||
a: require.New(t),
|
||||
roleID: id.Next(),
|
||||
cUser: &sysTypes.User{
|
||||
ID: id.Next(),
|
||||
},
|
||||
}
|
||||
|
||||
h.cUser.SetRoles(h.roleID)
|
||||
helpers.UpdateRBAC(h.roleID)
|
||||
|
||||
var err error
|
||||
ctx := context.Background()
|
||||
h.token, err = auth.TokenIssuer.Issue(ctx, auth.WithIdentity(h.cUser))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
}
|
||||
|
||||
// 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 (h helper) setLabel(res label.LabeledResource, name, value string) {
|
||||
h.a.NoError(store.UpsertLabel(h.secCtx(), service.DefaultStore, <ype.Label{
|
||||
Kind: res.LabelResourceKind(),
|
||||
ResourceID: res.LabelResourceID(),
|
||||
Name: name,
|
||||
Value: ltype.LabelValue{Val: value},
|
||||
}))
|
||||
}
|
||||
115
server/tests/automation/permissions_test.go
Normal file
115
server/tests/automation/permissions_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package automation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/automation/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(), "workflow.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().
|
||||
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(), "workflow.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":"workflow.create","access":"allow"}]}`, types.ComponentRbacResource())).
|
||||
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(), "workflow.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()
|
||||
}
|
||||
86
server/tests/automation/session_test.go
Normal file
86
server/tests/automation/session_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package automation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/automation/service"
|
||||
"github.com/cortezaproject/corteza/server/automation/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) clearSessions() {
|
||||
h.noError(store.TruncateAutomationSessions(context.Background(), service.DefaultStore))
|
||||
}
|
||||
|
||||
func (h helper) repoMakeSession(wf *types.Workflow, ss ...string) *types.Session {
|
||||
var r = &types.Session{
|
||||
ID: id.Next(),
|
||||
CreatedAt: time.Now(),
|
||||
WorkflowID: wf.ID,
|
||||
}
|
||||
|
||||
if len(ss) > 1 {
|
||||
r.ResourceType = ss[1]
|
||||
} else {
|
||||
r.ResourceType = "h_" + rs()
|
||||
|
||||
}
|
||||
|
||||
h.a.NoError(store.CreateAutomationSession(context.Background(), service.DefaultStore, r))
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func TestSessionList(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearSessions()
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "sessions.search")
|
||||
|
||||
wf := h.repoMakeWorkflow()
|
||||
h.repoMakeSession(wf)
|
||||
h.repoMakeSession(wf)
|
||||
|
||||
h.apiInit().
|
||||
Get("/sessions/").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Len(`$.response.set`, 2)).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestSessionRead(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearTriggers()
|
||||
|
||||
wf := h.repoMakeWorkflow()
|
||||
s := h.repoMakeSession(wf)
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "sessions.search")
|
||||
helpers.AllowMe(h, types.WorkflowRbacResource(0), "sessions.manage")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/sessions/%d", s.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestSessionTrace(t *testing.T) {
|
||||
t.Skip("pending implementation")
|
||||
}
|
||||
|
||||
func TestSessionDelete(t *testing.T) {
|
||||
t.Skip("pending implementation")
|
||||
}
|
||||
387
server/tests/automation/trigger_test.go
Normal file
387
server/tests/automation/trigger_test.go
Normal file
@@ -0,0 +1,387 @@
|
||||
package automation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/automation/service"
|
||||
"github.com/cortezaproject/corteza/server/automation/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/expr"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
"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"
|
||||
"github.com/steinfletcher/apitest"
|
||||
"github.com/steinfletcher/apitest-jsonpath"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func (h helper) clearTriggers() {
|
||||
h.noError(store.TruncateAutomationTriggers(context.Background(), service.DefaultStore))
|
||||
}
|
||||
|
||||
func (h helper) createTrigger(res *types.Trigger) *types.Trigger {
|
||||
if res.ID == 0 {
|
||||
res.ID = id.Next()
|
||||
}
|
||||
|
||||
if res.CreatedAt.IsZero() {
|
||||
res.CreatedAt = time.Now()
|
||||
}
|
||||
|
||||
h.a.NoError(service.DefaultStore.CreateAutomationTrigger(context.Background(), res))
|
||||
return res
|
||||
}
|
||||
|
||||
func (h helper) repoMakeTrigger(wf *types.Workflow, ss ...string) *types.Trigger {
|
||||
var r = &types.Trigger{
|
||||
ID: id.Next(),
|
||||
CreatedAt: time.Now(),
|
||||
WorkflowID: wf.ID,
|
||||
}
|
||||
|
||||
if len(ss) > 1 {
|
||||
r.ResourceType = ss[1]
|
||||
} else {
|
||||
r.ResourceType = "h_" + rs()
|
||||
|
||||
}
|
||||
|
||||
r.Enabled = true
|
||||
|
||||
h.a.NoError(store.CreateAutomationTrigger(context.Background(), service.DefaultStore, r))
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (h helper) lookupTriggerByID(ID uint64) *types.Trigger {
|
||||
res, err := store.LookupAutomationTriggerByID(context.Background(), service.DefaultStore, ID)
|
||||
h.noError(err)
|
||||
return res
|
||||
}
|
||||
|
||||
func TestTriggerRead(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearTriggers()
|
||||
|
||||
wf := h.repoMakeWorkflow()
|
||||
tg := h.repoMakeTrigger(wf)
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "triggers.search")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/triggers/%d", tg.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Equal(`$.response.resourceType`, tg.ResourceType)).
|
||||
Assert(jsonpath.Equal(`$.response.triggerID`, fmt.Sprintf("%d", tg.ID))).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestTriggerList(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearTriggers()
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "triggers.search")
|
||||
|
||||
wf := h.repoMakeWorkflow()
|
||||
h.repoMakeTrigger(wf)
|
||||
h.repoMakeTrigger(wf)
|
||||
|
||||
h.apiInit().
|
||||
Get("/triggers/").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Len(`$.response.set`, 2)).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestTriggerCreate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearTriggers()
|
||||
h.clearWorkflows()
|
||||
|
||||
var (
|
||||
wf = h.repoMakeWorkflow()
|
||||
req = func() *apitest.Request {
|
||||
return h.apiInit().
|
||||
Post("/triggers/").
|
||||
Header("Accept", "application/json").
|
||||
FormData("name", rs()).
|
||||
FormData("workflowID", fmt.Sprintf("%d", wf.ID))
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
t.Run("allowed", func(t *testing.T) {
|
||||
helpers.AllowMe(h, wf.RbacResource(), "triggers.manage")
|
||||
req().Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
})
|
||||
|
||||
t.Run("denied", func(t *testing.T) {
|
||||
helpers.DenyMe(h, wf.RbacResource(), "triggers.manage")
|
||||
req().Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("trigger.errors.notAllowedToCreate")).
|
||||
End()
|
||||
})
|
||||
}
|
||||
|
||||
func TestTriggerCreateFull(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
|
||||
helpers.AllowMe(h, types.WorkflowRbacResource(0), "triggers.manage")
|
||||
|
||||
h.clearTriggers()
|
||||
var (
|
||||
wf = h.repoMakeWorkflow()
|
||||
output = &types.Trigger{}
|
||||
stored = &types.Trigger{}
|
||||
input = &types.Trigger{
|
||||
WorkflowID: wf.ID,
|
||||
ResourceType: "wf-full-test",
|
||||
Enabled: true,
|
||||
Input: expr.EmptyVars(),
|
||||
OwnedBy: 42,
|
||||
}
|
||||
)
|
||||
|
||||
h.apiInit().
|
||||
Post("/triggers/").
|
||||
Header("Accept", "application/json").
|
||||
JSON(helpers.JSON(input)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End().
|
||||
JSON(&struct{ Response *types.Trigger }{output})
|
||||
|
||||
h.a.NotZero(output.ID)
|
||||
h.a.NotZero(output.OwnedBy)
|
||||
h.a.NotNil(output.CreatedAt)
|
||||
h.a.NotZero(output.CreatedBy)
|
||||
|
||||
input.ID = output.ID
|
||||
input.OwnedBy = output.OwnedBy
|
||||
input.CreatedBy = output.CreatedBy
|
||||
input.CreatedAt = output.CreatedAt
|
||||
h.a.NoError(output.Input.ResolveTypes(service.Registry().Type))
|
||||
|
||||
input.Meta = output.Meta
|
||||
|
||||
h.a.Equal(input, output)
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "triggers.search")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/triggers/%d", output.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End().
|
||||
JSON(&struct{ Response *types.Trigger }{stored})
|
||||
|
||||
h.a.NoError(stored.Input.ResolveTypes(service.Registry().Type))
|
||||
input.Meta = stored.Meta
|
||||
|
||||
h.a.Equal(input, stored)
|
||||
}
|
||||
|
||||
func TestTriggerUpdate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearTriggers()
|
||||
h.clearWorkflows()
|
||||
|
||||
var (
|
||||
wf = h.repoMakeWorkflow()
|
||||
tg1 = h.createTrigger(&types.Trigger{WorkflowID: wf.ID, ResourceType: "as-created"})
|
||||
tg2 = h.createTrigger(&types.Trigger{WorkflowID: wf.ID, ResourceType: "as-created"})
|
||||
|
||||
req = func(id uint64, resType string) *apitest.Request {
|
||||
return h.apiInit().
|
||||
Put(fmt.Sprintf("/triggers/%d", id)).
|
||||
Header("Accept", "application/json").
|
||||
FormData("resourceType", resType)
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
t.Run("allowed", func(t *testing.T) {
|
||||
helpers.AllowMe(h, wf.RbacResource(), "triggers.manage")
|
||||
req(tg1.ID, "foo").Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
res := h.lookupTriggerByID(tg1.ID)
|
||||
h.a.NotNil(res)
|
||||
h.a.Equal("foo", res.ResourceType)
|
||||
})
|
||||
|
||||
t.Run("denied", func(t *testing.T) {
|
||||
helpers.DenyMe(h, wf.RbacResource(), "triggers.manage")
|
||||
req(tg2.ID, "bar").Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("trigger.errors.notAllowedToUpdate")).
|
||||
End()
|
||||
|
||||
res := h.lookupTriggerByID(tg2.ID)
|
||||
h.a.NotNil(res)
|
||||
h.a.NotEqual("bar", res.ResourceType)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTriggerDeleteForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
|
||||
wf := h.repoMakeWorkflow()
|
||||
res := h.repoMakeTrigger(wf)
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/triggers/%d", res.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("trigger.errors.notAllowedToDelete")).
|
||||
End()
|
||||
|
||||
res = h.lookupTriggerByID(res.ID)
|
||||
h.a.NotNil(res)
|
||||
h.a.Nil(res.DeletedAt)
|
||||
}
|
||||
|
||||
func TestTriggerDelete(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
helpers.AllowMe(h, types.WorkflowRbacResource(0), "triggers.manage")
|
||||
|
||||
wf := h.repoMakeWorkflow()
|
||||
res := h.repoMakeTrigger(wf)
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/triggers/%d", res.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
res = h.lookupTriggerByID(res.ID)
|
||||
h.a.NotNil(res)
|
||||
h.a.NotNil(res.DeletedAt)
|
||||
}
|
||||
|
||||
func TestTriggerUndelete(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
helpers.AllowMe(h, types.WorkflowRbacResource(0), "triggers.manage")
|
||||
|
||||
wf := h.repoMakeWorkflow()
|
||||
res := h.repoMakeTrigger(wf)
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/triggers/%d/undelete", res.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
res = h.lookupTriggerByID(res.ID)
|
||||
h.a.NotNil(res)
|
||||
h.a.Nil(res.DeletedAt)
|
||||
}
|
||||
|
||||
func TestTriggerLabels(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearTriggers()
|
||||
|
||||
helpers.AllowMe(h, types.WorkflowRbacResource(0), "triggers.manage")
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "triggers.search")
|
||||
|
||||
var (
|
||||
ID uint64
|
||||
wf = h.repoMakeWorkflow()
|
||||
)
|
||||
|
||||
t.Run("create", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Trigger{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
"/triggers/",
|
||||
types.Trigger{Labels: map[string]labelTypes.LabelValue{"foo": {Val: "bar"}, "bar": {Val: "42"}}, WorkflowID: wf.ID},
|
||||
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.Trigger{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("PUT /triggers/%d", ID),
|
||||
&types.Trigger{Labels: map[string]labelTypes.LabelValue{"foo": {Val: "baz"}, "baz": {Val: "123"}}, WorkflowID: wf.ID},
|
||||
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.TriggerSet{}
|
||||
params = url.Values{}
|
||||
)
|
||||
params.Add("labels", "baz=123")
|
||||
params.Add("disabled", filter.StateInclusive.String())
|
||||
|
||||
helpers.SearchWithLabelsViaAPI(h.apiInit(), t, "/triggers/", &set, params)
|
||||
req.NotEmpty(set)
|
||||
req.NotNil(set.FindByID(ID))
|
||||
req.NotNil(set.FindByID(ID).Labels)
|
||||
})
|
||||
}
|
||||
504
server/tests/automation/workflow_test.go
Normal file
504
server/tests/automation/workflow_test.go
Normal file
@@ -0,0 +1,504 @@
|
||||
package automation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/automation/service"
|
||||
"github.com/cortezaproject/corteza/server/automation/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/expr"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
"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"
|
||||
"github.com/steinfletcher/apitest-jsonpath"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func (h helper) clearWorkflows() {
|
||||
h.noError(store.TruncateAutomationWorkflows(context.Background(), service.DefaultStore))
|
||||
}
|
||||
|
||||
func (h helper) createWorkflow(res *types.Workflow) *types.Workflow {
|
||||
if res.ID == 0 {
|
||||
res.ID = id.Next()
|
||||
}
|
||||
|
||||
if res.CreatedAt.IsZero() {
|
||||
res.CreatedAt = time.Now()
|
||||
}
|
||||
|
||||
h.a.NoError(service.DefaultStore.CreateAutomationWorkflow(context.Background(), res))
|
||||
return res
|
||||
}
|
||||
|
||||
func (h helper) repoMakeWorkflow(ss ...string) *types.Workflow {
|
||||
var r = &types.Workflow{
|
||||
ID: id.Next(),
|
||||
CreatedAt: time.Now(),
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
if len(ss) > 1 {
|
||||
r.Handle = ss[1]
|
||||
} else {
|
||||
r.Handle = "h_" + rs()
|
||||
|
||||
}
|
||||
|
||||
h.a.NoError(store.CreateAutomationWorkflow(context.Background(), service.DefaultStore, r))
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (h helper) lookupWorkflowByID(ID uint64) *types.Workflow {
|
||||
res, err := store.LookupAutomationWorkflowByID(context.Background(), service.DefaultStore, ID)
|
||||
h.noError(err)
|
||||
return res
|
||||
}
|
||||
|
||||
func TestWorkflowRead(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearWorkflows()
|
||||
|
||||
wf := h.repoMakeWorkflow()
|
||||
helpers.AllowMe(h, wf.RbacResource(), "read")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/workflows/%d", wf.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Equal(`$.response.handle`, wf.Handle)).
|
||||
Assert(jsonpath.Equal(`$.response.workflowID`, fmt.Sprintf("%d", wf.ID))).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestWorkflowList(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearWorkflows()
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "workflows.search")
|
||||
helpers.AllowMe(h, types.WorkflowRbacResource(0), "read")
|
||||
|
||||
h.repoMakeWorkflow()
|
||||
h.repoMakeWorkflow()
|
||||
|
||||
h.apiInit().
|
||||
Get("/workflows/").
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Len(`$.response.set`, 2)).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestWorkflowList_filterForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
|
||||
h.repoMakeWorkflow("workflow")
|
||||
f := h.repoMakeWorkflow()
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "workflows.search")
|
||||
helpers.DenyMe(h, f.RbacResource(), "read")
|
||||
|
||||
h.apiInit().
|
||||
Get("/workflows/").
|
||||
Query("handle", f.Handle).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.NotPresent(fmt.Sprintf(`$.response.set[? @.handle=="%s"]`, f.Handle))).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestWorkflowCreateForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
|
||||
h.apiInit().
|
||||
Post("/workflows/").
|
||||
Header("Accept", "application/json").
|
||||
FormData("name", rs()).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("workflow.errors.notAllowedToCreate")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestWorkflowCreateNotUnique(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearWorkflows()
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "workflow.create")
|
||||
|
||||
workflow := h.repoMakeWorkflow()
|
||||
h.apiInit().
|
||||
Post("/workflows/").
|
||||
Header("Accept", "application/json").
|
||||
FormData("meta", fmt.Sprintf(`{"name": "%s"}`, rs())).
|
||||
FormData("handle", workflow.Handle).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("workflow.errors.handleNotUnique")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestWorkflowCreate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearWorkflows()
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "workflow.create")
|
||||
|
||||
h.apiInit().
|
||||
Post("/workflows/").
|
||||
FormData("meta", fmt.Sprintf(`{"name": "%s"}`, rs())).
|
||||
FormData("handle", "handle_"+rs()).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestWorkflowCreateFull(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "workflow.create")
|
||||
|
||||
h.clearWorkflows()
|
||||
var (
|
||||
output = &types.Workflow{}
|
||||
stored = &types.Workflow{}
|
||||
input = &types.Workflow{
|
||||
Handle: "wf-full-test",
|
||||
Meta: &types.WorkflowMeta{
|
||||
Name: "name",
|
||||
Description: "desc",
|
||||
Visual: map[string]interface{}{"foo": "bar"},
|
||||
},
|
||||
Enabled: true,
|
||||
Trace: true,
|
||||
KeepSessions: 10000,
|
||||
Scope: func() *expr.Vars {
|
||||
o := &expr.Vars{}
|
||||
_ = o.Set("foo", "bar")
|
||||
return o
|
||||
}(),
|
||||
Steps: types.WorkflowStepSet{},
|
||||
Paths: types.WorkflowPathSet{},
|
||||
RunAs: 0,
|
||||
OwnedBy: 42,
|
||||
}
|
||||
)
|
||||
|
||||
h.apiInit().
|
||||
Post("/workflows/").
|
||||
Header("Accept", "application/json").
|
||||
JSON(helpers.JSON(input)).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End().
|
||||
JSON(&struct{ Response *types.Workflow }{output})
|
||||
|
||||
h.a.NotZero(output.ID)
|
||||
h.a.NotZero(output.OwnedBy)
|
||||
h.a.NotNil(output.CreatedAt)
|
||||
h.a.NotZero(output.CreatedBy)
|
||||
|
||||
// Make comparing input&output a bit easier
|
||||
input.ID = output.ID
|
||||
input.OwnedBy = output.OwnedBy
|
||||
input.CreatedBy = output.CreatedBy
|
||||
input.CreatedAt = output.CreatedAt
|
||||
h.a.NoError(output.Scope.ResolveTypes(service.Registry().Type))
|
||||
|
||||
h.a.Equal(input, output)
|
||||
|
||||
helpers.AllowMe(h, output.RbacResource(), "read")
|
||||
|
||||
h.apiInit().
|
||||
Get(fmt.Sprintf("/workflows/%d", output.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End().
|
||||
JSON(&struct{ Response *types.Workflow }{stored})
|
||||
|
||||
h.a.NoError(stored.Scope.ResolveTypes(service.Registry().Type))
|
||||
|
||||
h.a.Equal(input, stored)
|
||||
}
|
||||
|
||||
func TestWorkflowUpdateForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearWorkflows()
|
||||
u := h.repoMakeWorkflow()
|
||||
|
||||
h.apiInit().
|
||||
Put(fmt.Sprintf("/workflows/%d", u.ID)).
|
||||
Header("Accept", "application/json").
|
||||
FormData("meta", fmt.Sprintf(`{"name": "%s"}`, rs())).
|
||||
FormData("email", rs()).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("workflow.errors.notAllowedToUpdate")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestWorkflowUpdate(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearWorkflows()
|
||||
res := h.repoMakeWorkflow()
|
||||
helpers.AllowMe(h, types.WorkflowRbacResource(0), "update")
|
||||
|
||||
newName := "updated-" + rs()
|
||||
newHandle := "updated-" + rs()
|
||||
|
||||
h.apiInit().
|
||||
Put(fmt.Sprintf("/workflows/%d", res.ID)).
|
||||
FormData("meta", fmt.Sprintf(`{"name": "%s"}`, newName)).
|
||||
FormData("handle", newHandle).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
res = h.lookupWorkflowByID(res.ID)
|
||||
h.a.NotNil(res)
|
||||
h.a.Equal(newHandle, res.Handle)
|
||||
}
|
||||
|
||||
func TestWorkflowDeleteForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
u := h.repoMakeWorkflow()
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/workflows/%d", u.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertError("workflow.errors.notAllowedToDelete")).
|
||||
End()
|
||||
}
|
||||
|
||||
func TestWorkflowDelete(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
helpers.AllowMe(h, types.WorkflowRbacResource(0), "delete")
|
||||
|
||||
res := h.repoMakeWorkflow()
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/workflows/%d", res.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
res = h.lookupWorkflowByID(res.ID)
|
||||
h.a.NotNil(res)
|
||||
h.a.NotNil(res.DeletedAt)
|
||||
}
|
||||
|
||||
func TestWorkflowUndelete(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
helpers.AllowMe(h, types.WorkflowRbacResource(0), "undelete")
|
||||
|
||||
res := h.repoMakeWorkflow()
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/workflows/%d/undelete", res.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
res = h.lookupWorkflowByID(res.ID)
|
||||
h.a.NotNil(res)
|
||||
h.a.Nil(res.DeletedAt)
|
||||
}
|
||||
|
||||
func TestWorkflowLabels(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearWorkflows()
|
||||
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "workflow.create", "workflows.search")
|
||||
helpers.AllowMe(h, types.WorkflowRbacResource(0), "read", "update", "delete")
|
||||
|
||||
var (
|
||||
ID uint64
|
||||
)
|
||||
|
||||
t.Run("create", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
payload = &types.Workflow{Meta: &types.WorkflowMeta{Name: rs()}}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
"/workflows/",
|
||||
types.Workflow{Labels: map[string]labelTypes.LabelValue{"foo": {Val: "bar"}, "bar": {Val: "42"}}, Meta: &types.WorkflowMeta{Name: rs()}},
|
||||
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.Workflow{}
|
||||
)
|
||||
|
||||
helpers.SetLabelsViaAPI(h.apiInit(), t,
|
||||
fmt.Sprintf("PUT /workflows/%d", ID),
|
||||
&types.Workflow{Labels: map[string]labelTypes.LabelValue{"foo": {Val: "baz"}, "baz": {Val: "123"}}, Meta: &types.WorkflowMeta{Name: rs()}},
|
||||
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.WorkflowSet{}
|
||||
params = url.Values{}
|
||||
)
|
||||
|
||||
params.Add("labels", "baz=123")
|
||||
params.Add("disabled", filter.StateInclusive.String())
|
||||
|
||||
helpers.SearchWithLabelsViaAPI(h.apiInit(), t, "/workflows/", &set, params)
|
||||
req.NotEmpty(set)
|
||||
req.NotNil(set.FindByID(ID))
|
||||
req.NotNil(set.FindByID(ID).Labels)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkflowStepsPayload(t *testing.T) {
|
||||
wf := &types.Workflow{}
|
||||
h := newHelper(t)
|
||||
helpers.AllowMe(h, types.ComponentRbacResource(), "workflow.create")
|
||||
|
||||
h.apiInit().
|
||||
Post("/workflows/").
|
||||
JSON(`{
|
||||
"scope": {
|
||||
"foo": { "@type": "String", "@value": "" },
|
||||
"bar": { "@type": "String", "@value": "" },
|
||||
"baz": { "@type": "String", "@value": "" },
|
||||
"hgtg": { "@type": "Integer", "@value": 42 }
|
||||
},
|
||||
|
||||
"meta": {
|
||||
"name": "name of the workdflow",
|
||||
"description": "lorem ipsum dolor sit...",
|
||||
"visual": { "arbitrary key": "arbitrary value" }
|
||||
},
|
||||
|
||||
"steps": [
|
||||
{ "stepID": "100",
|
||||
"kind": "expressions",
|
||||
"arguments": [
|
||||
{ "name": "foo", "expr": "\"foo\"" },
|
||||
{ "name": "bar", "expr": "\"bar\"" }
|
||||
],
|
||||
"meta": { "name": "step name text", "description": "step description text", "visual": { "arbitrary key": "arbitrary value" }}
|
||||
},
|
||||
{ "stepID": "101",
|
||||
"kind": "expressions",
|
||||
|
||||
"arguments": [
|
||||
{ "name": "foo", "expr": "\"foo\"" },
|
||||
{ "name": "bar", "expr": "\"bar\"" }
|
||||
]
|
||||
},
|
||||
|
||||
{ "stepID": "200", "kind": "gateway", "ref": "join" },
|
||||
{ "stepID": "201", "kind": "gateway", "ref": "fork" },
|
||||
{ "stepID": "202", "kind": "gateway", "ref": "excl" },
|
||||
{ "stepID": "203", "kind": "gateway", "ref": "incl" },
|
||||
|
||||
{ "stepID": "300",
|
||||
"kind": "function",
|
||||
"ref": "serverSideFuncRef",
|
||||
"meta": { "description": "ref needs to come from a predefined list of registered functions; arguments pick values from scope and need to be compatible with registered function's parameters; same goes for results" },
|
||||
|
||||
"arguments": [
|
||||
{ "name": "arg1", "expr": "foo" },
|
||||
{ "name": "arg2", "expr": "hgtg * 2" }
|
||||
],
|
||||
|
||||
"results": [
|
||||
{ "name": "scopeVar1", "expr": "nameOfVarFromFunctionsResult1" },
|
||||
{ "name": "scopeVar2", "expr": "nameOfVarFromFunctionsResult2" }
|
||||
]
|
||||
},
|
||||
{ "stepID": "400",
|
||||
"kind": "prompt",
|
||||
"ref": "clientPromptRef",
|
||||
"meta": { "description": "backed does not need to know about ref, it only cares that input data (when resuming session) is compatible with arguments (and tests there)" },
|
||||
|
||||
"arguments": [
|
||||
{ "name": "foo", "expr": "inputVar1",
|
||||
"tests": [
|
||||
{ "expr": "inputVar1 != \"\"", "error": "expecting inputVar1 to be non-empty string" }
|
||||
]
|
||||
},
|
||||
{ "name": "bar", "expr": "inputVar2" }
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
"paths": [
|
||||
{ "parentID": "201", "childID": "300", "expr": "hgtg == 42",
|
||||
"meta": { "name": "...", "description": "only if equals 42" } },
|
||||
{ "parentID": "201", "childID": "400",
|
||||
"meta": { "name": "...", "description": "else" } }
|
||||
]
|
||||
}`).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End().
|
||||
JSON(&struct{ Response *types.Workflow }{Response: wf})
|
||||
}
|
||||
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 |
72
server/tests/dal/dal_codec_alias_test.go
Normal file
72
server/tests/dal/dal_codec_alias_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package dal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/dal"
|
||||
)
|
||||
|
||||
func Test_dal_codec_alias(t *testing.T) {
|
||||
t.Skip("needs refactoring")
|
||||
|
||||
model := &dal.Model{
|
||||
Ident: "compose_record_partitioned",
|
||||
Attributes: dal.AttributeSet{
|
||||
&dal.Attribute{Ident: "ID", Type: &dal.TypeID{}, Store: &dal.CodecAlias{Ident: "id"}, PrimaryKey: true},
|
||||
|
||||
&dal.Attribute{Ident: "intr_vID", Type: &dal.TypeID{}, Store: &dal.CodecAlias{Ident: "vID"}},
|
||||
&dal.Attribute{Ident: "intr_vRef", Type: &dal.TypeRef{}, Store: &dal.CodecAlias{Ident: "vRef"}},
|
||||
&dal.Attribute{Ident: "intr_vTimestamp", Type: &dal.TypeTimestamp{}, Store: &dal.CodecAlias{Ident: "vTimestamp"}},
|
||||
&dal.Attribute{Ident: "intr_vTime", Type: &dal.TypeTime{}, Store: &dal.CodecAlias{Ident: "vTime"}},
|
||||
&dal.Attribute{Ident: "intr_vDate", Type: &dal.TypeDate{}, Store: &dal.CodecAlias{Ident: "vDate"}},
|
||||
&dal.Attribute{Ident: "intr_vNumber", Type: &dal.TypeNumber{}, Store: &dal.CodecAlias{Ident: "vNumber"}},
|
||||
&dal.Attribute{Ident: "intr_vText", Type: &dal.TypeText{}, Store: &dal.CodecAlias{Ident: "vText"}},
|
||||
&dal.Attribute{Ident: "intr_vBoolean_T", Type: &dal.TypeBoolean{}, Store: &dal.CodecAlias{Ident: "vBoolean_T"}},
|
||||
&dal.Attribute{Ident: "intr_vBoolean_F", Type: &dal.TypeBoolean{}, Store: &dal.CodecAlias{Ident: "vBoolean_F"}},
|
||||
&dal.Attribute{Ident: "intr_vEnum", Type: &dal.TypeEnum{}, Store: &dal.CodecAlias{Ident: "vEnum"}},
|
||||
&dal.Attribute{Ident: "intr_vGeometry", Type: &dal.TypeGeometry{}, Store: &dal.CodecAlias{Ident: "vGeometry"}},
|
||||
&dal.Attribute{Ident: "intr_vJSON", Type: &dal.TypeJSON{}, Store: &dal.CodecAlias{Ident: "vJSON"}},
|
||||
&dal.Attribute{Ident: "intr_vBlob", Type: &dal.TypeBlob{}, Store: &dal.CodecAlias{Ident: "vBlob"}},
|
||||
&dal.Attribute{Ident: "intr_vUUID", Type: &dal.TypeUUID{}, Store: &dal.CodecAlias{Ident: "vUUID"}},
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
rIn = types.Record{ID: 42, CreatedAt: time.Now()}
|
||||
rOut = types.Record{}
|
||||
)
|
||||
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "intr_vID", Value: "34324"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "intr_vRef", Value: "32423"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "intr_vTimestamp", Value: "2022-01-01T10:10:10"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "intr_vTime", Value: "04:10:10"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "intr_vDate", Value: "2022-01-01"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "intr_vNumber", Value: "2423423"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "intr_vText", Value: "lorm ipsum "})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "intr_vBoolean_T", Value: "1"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "intr_vBoolean_F", Value: ""})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "intr_vEnum", Value: "abc"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "intr_vGeometry", Value: `{"lat":1,"lng":1}`})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "intr_vJSON", Value: `[{"bool":true"}]`})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "intr_vBlob", Value: "0110101"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "intr_vUUID", Value: "ba485865-54f9-44de-bde8-6965556c022a"})
|
||||
|
||||
bootstrap(t, func(ctx context.Context, t *testing.T, h helper, svc dal.FullService) {
|
||||
h.cleanupDal()
|
||||
|
||||
_, err := svc.ReplaceModel(ctx, nil, model)
|
||||
h.a.NoError(err)
|
||||
|
||||
h.a.NoError(svc.Create(ctx, model.ToFilter(), dal.CreateOperations(), &rIn))
|
||||
|
||||
h.a.NoError(svc.Lookup(ctx, model.ToFilter(), dal.LookupOperations(), dal.PKValues{"id": rIn.ID}, &rOut))
|
||||
|
||||
for _, inVal := range rIn.Values {
|
||||
outVal := rOut.Values.Get(inVal.Name, 0)
|
||||
h.a.Equal(inVal.Value, outVal.Value, "field", inVal.Name)
|
||||
}
|
||||
})
|
||||
}
|
||||
68
server/tests/dal/dal_codec_json_test.go
Normal file
68
server/tests/dal/dal_codec_json_test.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package dal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/dal"
|
||||
)
|
||||
|
||||
func Test_dal_codec_json(t *testing.T) {
|
||||
t.Skip("needs refactoring")
|
||||
|
||||
model := &dal.Model{
|
||||
Ident: "compose_record",
|
||||
Attributes: dal.AttributeSet{
|
||||
&dal.Attribute{Ident: "ID", Type: &dal.TypeID{}, Store: &dal.CodecAlias{Ident: "id"}, PrimaryKey: true},
|
||||
|
||||
&dal.Attribute{Ident: "vID", Type: &dal.TypeID{}, Store: &dal.CodecRecordValueSetJSON{Ident: "values"}},
|
||||
&dal.Attribute{Ident: "vRef", Type: &dal.TypeRef{}, Store: &dal.CodecRecordValueSetJSON{Ident: "values"}},
|
||||
&dal.Attribute{Ident: "vTimestamp", Type: &dal.TypeTimestamp{}, Store: &dal.CodecRecordValueSetJSON{Ident: "values"}},
|
||||
&dal.Attribute{Ident: "vTime", Type: &dal.TypeTime{}, Store: &dal.CodecRecordValueSetJSON{Ident: "values"}},
|
||||
&dal.Attribute{Ident: "vDate", Type: &dal.TypeDate{}, Store: &dal.CodecRecordValueSetJSON{Ident: "values"}},
|
||||
&dal.Attribute{Ident: "vNumber", Type: &dal.TypeNumber{}, Store: &dal.CodecRecordValueSetJSON{Ident: "values"}},
|
||||
&dal.Attribute{Ident: "vText", Type: &dal.TypeText{}, Store: &dal.CodecRecordValueSetJSON{Ident: "values"}},
|
||||
&dal.Attribute{Ident: "vBoolean_T", Type: &dal.TypeBoolean{}, Store: &dal.CodecRecordValueSetJSON{Ident: "values"}},
|
||||
&dal.Attribute{Ident: "vBoolean_F", Type: &dal.TypeBoolean{}, Store: &dal.CodecRecordValueSetJSON{Ident: "values"}},
|
||||
&dal.Attribute{Ident: "vEnum", Type: &dal.TypeEnum{}, Store: &dal.CodecRecordValueSetJSON{Ident: "values"}},
|
||||
&dal.Attribute{Ident: "vGeometry", Type: &dal.TypeGeometry{}, Store: &dal.CodecRecordValueSetJSON{Ident: "values"}},
|
||||
&dal.Attribute{Ident: "vJSON", Type: &dal.TypeJSON{}, Store: &dal.CodecRecordValueSetJSON{Ident: "values"}},
|
||||
&dal.Attribute{Ident: "vBlob", Type: &dal.TypeBlob{}, Store: &dal.CodecRecordValueSetJSON{Ident: "values"}},
|
||||
&dal.Attribute{Ident: "vUUID", Type: &dal.TypeUUID{}, Store: &dal.CodecRecordValueSetJSON{Ident: "values"}},
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
rIn = types.Record{ID: 42}
|
||||
rOut = types.Record{}
|
||||
)
|
||||
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vID", Value: "34324"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vRef", Value: "32423"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vTimestamp", Value: "2022-01-01T10:10:10"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vTime", Value: "04:10:10"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vDate", Value: "2022-01-01"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vNumber", Value: "2423423"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vText", Value: "lorm ipsum "})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vBoolean_T", Value: "1"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vBoolean_F", Value: ""})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vEnum", Value: "abc"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vGeometry", Value: `{"lat":1,"lng":1}`})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vJSON", Value: `[{"bool":true"}]`})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vBlob", Value: "0110101"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vUUID", Value: "ba485865-54f9-44de-bde8-6965556c022a"})
|
||||
|
||||
bootstrap(t, func(ctx context.Context, t *testing.T, h helper, svc dal.FullService) {
|
||||
_, err := svc.ReplaceModel(ctx, nil, model)
|
||||
h.a.NoError(err)
|
||||
h.a.NoError(svc.Create(ctx, model.ToFilter(), dal.CreateOperations(), &rIn))
|
||||
|
||||
h.a.NoError(svc.Lookup(ctx, model.ToFilter(), dal.LookupOperations(), dal.PKValues{"id": rIn.ID}, &rOut))
|
||||
|
||||
for _, inVal := range rIn.Values {
|
||||
outVal := rOut.Values.Get(inVal.Name, 0)
|
||||
h.a.Equal(inVal.Value, outVal.Value, "field", inVal.Name)
|
||||
}
|
||||
})
|
||||
}
|
||||
141
server/tests/dal/dal_codec_plain_test.go
Normal file
141
server/tests/dal/dal_codec_plain_test.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package dal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/dal"
|
||||
)
|
||||
|
||||
func Test_dal_codec_plain(t *testing.T) {
|
||||
t.Skip("needs refactoring")
|
||||
|
||||
model := &dal.Model{
|
||||
Ident: "compose_record_partitioned",
|
||||
Attributes: dal.AttributeSet{
|
||||
&dal.Attribute{Ident: "ID", Type: &dal.TypeID{}, Store: &dal.CodecAlias{Ident: "id"}, PrimaryKey: true},
|
||||
|
||||
&dal.Attribute{Ident: "vID", Type: &dal.TypeID{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vRef", Type: &dal.TypeRef{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vTimestamp", Type: &dal.TypeTimestamp{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vTime", Type: &dal.TypeTime{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vDate", Type: &dal.TypeDate{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vNumber", Type: &dal.TypeNumber{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vText", Type: &dal.TypeText{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vBoolean_T", Type: &dal.TypeBoolean{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vBoolean_F", Type: &dal.TypeBoolean{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vEnum", Type: &dal.TypeEnum{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vGeometry", Type: &dal.TypeGeometry{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vJSON", Type: &dal.TypeJSON{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vBlob", Type: &dal.TypeBlob{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vUUID", Type: &dal.TypeUUID{}, Store: &dal.CodecPlain{}},
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
rIn = types.Record{ID: 42}
|
||||
rOut = types.Record{}
|
||||
)
|
||||
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vID", Value: "34324"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vRef", Value: "32423"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vTimestamp", Value: "2022-01-01T10:10:10"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vTime", Value: "04:10:10"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vDate", Value: "2022-01-01"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vNumber", Value: "2423423"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vText", Value: "lorm ipsum "})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vBoolean_T", Value: "1"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vBoolean_F", Value: ""})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vEnum", Value: "abc"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vGeometry", Value: `{"lat":1,"lng":1}`})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vJSON", Value: `[{"bool":true"}]`})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vBlob", Value: "0110101"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vUUID", Value: "ba485865-54f9-44de-bde8-6965556c022a"})
|
||||
|
||||
bootstrap(t, func(ctx context.Context, t *testing.T, h helper, svc dal.FullService) {
|
||||
_, err := svc.ReplaceModel(ctx, nil, model)
|
||||
h.a.NoError(err)
|
||||
h.a.NoError(svc.Create(ctx, model.ToFilter(), dal.CreateOperations(), &rIn))
|
||||
|
||||
h.a.NoError(svc.Lookup(ctx, model.ToFilter(), dal.LookupOperations(), dal.PKValues{"id": rIn.ID}, &rOut))
|
||||
|
||||
for _, inVal := range rIn.Values {
|
||||
outVal := rOut.Values.Get(inVal.Name, 0)
|
||||
h.a.Equal(inVal.Value, outVal.Value, "field", inVal.Name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func benchmark_dal_codec_plain(b *testing.B, count int) {
|
||||
b.Skip("needs refactoring")
|
||||
|
||||
model := &dal.Model{
|
||||
Ident: "compose_record_partitioned",
|
||||
Attributes: dal.AttributeSet{
|
||||
&dal.Attribute{Ident: "ID", Type: &dal.TypeID{}, Store: &dal.CodecAlias{Ident: "id"}, PrimaryKey: true},
|
||||
|
||||
&dal.Attribute{Ident: "vID", Type: &dal.TypeID{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vRef", Type: &dal.TypeRef{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vTimestamp", Type: &dal.TypeTimestamp{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vTime", Type: &dal.TypeTime{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vDate", Type: &dal.TypeDate{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vNumber", Type: &dal.TypeNumber{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vText", Type: &dal.TypeText{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vBoolean_T", Type: &dal.TypeBoolean{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vBoolean_F", Type: &dal.TypeBoolean{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vEnum", Type: &dal.TypeEnum{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vGeometry", Type: &dal.TypeGeometry{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vJSON", Type: &dal.TypeJSON{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vBlob", Type: &dal.TypeBlob{}, Store: &dal.CodecPlain{}},
|
||||
&dal.Attribute{Ident: "vUUID", Type: &dal.TypeUUID{}, Store: &dal.CodecPlain{}},
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
rIn = types.Record{ID: 42}
|
||||
)
|
||||
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vID", Value: "34324"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vRef", Value: "32423"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vTimestamp", Value: "2022-01-01T10:10:10"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vTime", Value: "04:10:10"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vDate", Value: "2022-01-01"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vNumber", Value: "2423423"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vText", Value: "lorm ipsum "})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vBoolean_T", Value: "true"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vBoolean_F", Value: "false"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vEnum", Value: "abc"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vGeometry", Value: `{"lat":1,"lng":1}`})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vJSON", Value: `[{"bool":true"}]`})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vBlob", Value: "0110101"})
|
||||
rIn.Values = rIn.Values.Set(&types.RecordValue{Name: "vUUID", Value: "ba485865-54f9-44de-bde8-6965556c022a"})
|
||||
|
||||
bootstrapBenchmark(b, func(ctx context.Context, b *testing.B, h helper, svc dal.FullService) {
|
||||
_, err := svc.ReplaceModel(ctx, nil, model)
|
||||
h.a.NoError(err)
|
||||
|
||||
ctr := uint64(1)
|
||||
for rep := 0; rep < b.N; rep++ {
|
||||
insert := make([]dal.ValueGetter, 0, 100)
|
||||
for i := 0; i < 100; i++ {
|
||||
tmp := rIn
|
||||
tmp.ID = ctr
|
||||
ctr++
|
||||
|
||||
insert = append(insert, &tmp)
|
||||
}
|
||||
|
||||
b.StartTimer()
|
||||
h.a.NoError(svc.Create(ctx, model.ToFilter(), dal.CreateOperations(), insert...))
|
||||
b.StopTimer()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_dal_codec_plain_1(b *testing.B) { benchmark_dal_codec_plain(b, 1) }
|
||||
func Benchmark_dal_codec_plain_10(b *testing.B) { benchmark_dal_codec_plain(b, 10) }
|
||||
func Benchmark_dal_codec_plain_100(b *testing.B) { benchmark_dal_codec_plain(b, 100) }
|
||||
|
||||
func Benchmark_dal_codec_plain_1000(b *testing.B) { benchmark_dal_codec_plain(b, 1000) }
|
||||
func Benchmark_dal_codec_plain_10000(b *testing.B) { benchmark_dal_codec_plain(b, 10000) }
|
||||
193
server/tests/dal/dal_crud_compose_record_test.go
Normal file
193
server/tests/dal/dal_crud_compose_record_test.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package dal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
)
|
||||
|
||||
func Test_dal_crud_compose_record_create(t *testing.T) {
|
||||
t.Skip("needs refactoring")
|
||||
|
||||
h := newHelperT(t)
|
||||
defer h.cleanupDal()
|
||||
|
||||
ctx := h.secCtx()
|
||||
|
||||
helpers.AllowMeModuleCRUD(h)
|
||||
helpers.AllowMeRecordCRUD(h)
|
||||
|
||||
ns := h.createNamespace("test")
|
||||
module := createModuleFromGenerics(ctx, t, "ok_module.json", ns.ID, &types.ModuleConfig{
|
||||
DAL: types.ModuleConfigDAL{
|
||||
ConnectionID: 0,
|
||||
},
|
||||
})
|
||||
|
||||
// create
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/compose/namespace/%d/module/%d/record/", ns.ID, module.ID)).
|
||||
Body(loadRequestFromGenerics(t, "ok_record.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
rsp := h.apiInit().
|
||||
Get(fmt.Sprintf("/compose/namespace/%d/module/%d/record/", ns.ID, module.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
aux := composeRecordSearchRestRsp{}
|
||||
dd := json.NewDecoder(rsp.Response.Body)
|
||||
h.a.NoError(dd.Decode(&aux))
|
||||
|
||||
r := aux.Response.Set[0]
|
||||
h.a.NotEqual(uint64(0), r.ID)
|
||||
h.a.Equal(module.ID, r.ModuleID)
|
||||
h.a.Equal(module.NamespaceID, r.NamespaceID)
|
||||
h.a.Equal(h.cUser.ID, r.OwnedBy)
|
||||
|
||||
h.a.NotEqual(time.Time{}, r.CreatedAt)
|
||||
h.a.Equal(h.cUser.ID, r.CreatedBy)
|
||||
|
||||
h.a.Nil(r.UpdatedAt)
|
||||
h.a.Equal(uint64(0), r.UpdatedBy)
|
||||
|
||||
h.a.Nil(r.DeletedAt)
|
||||
h.a.Equal(uint64(0), r.DeletedBy)
|
||||
|
||||
h.a.Equal("me@test.tld", r.Values.Get("email", 0).Value)
|
||||
h.a.Equal("Me", r.Values.Get("name", 0).Value)
|
||||
h.a.Equal("42", r.Values.Get("a_number", 0).Value)
|
||||
}
|
||||
|
||||
func Test_dal_crud_compose_record_update(t *testing.T) {
|
||||
t.Skip("needs refactoring")
|
||||
|
||||
h := newHelperT(t)
|
||||
defer h.cleanupDal()
|
||||
|
||||
ctx := h.secCtx()
|
||||
|
||||
helpers.AllowMeModuleCRUD(h)
|
||||
helpers.AllowMeRecordCRUD(h)
|
||||
|
||||
ns := h.createNamespace("test")
|
||||
module := createModuleFromGenerics(ctx, t, "ok_module.json", ns.ID, &types.ModuleConfig{
|
||||
DAL: types.ModuleConfigDAL{
|
||||
ConnectionID: 0,
|
||||
},
|
||||
})
|
||||
|
||||
record := createRecordFromGenerics(ctx, t, "ok_record.json", ns.ID, module.ID)
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/compose/namespace/%d/module/%d/record/%d", ns.ID, module.ID, record.ID)).
|
||||
Body(loadRequestFromGenerics(t, "ok_record_update.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
rsp := h.apiInit().
|
||||
Get(fmt.Sprintf("/compose/namespace/%d/module/%d/record/", ns.ID, module.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
aux := composeRecordSearchRestRsp{}
|
||||
dd := json.NewDecoder(rsp.Response.Body)
|
||||
h.a.NoError(dd.Decode(&aux))
|
||||
|
||||
r := aux.Response.Set[0]
|
||||
h.a.NotEqual(uint64(0), r.ID)
|
||||
h.a.Equal(module.ID, r.ModuleID)
|
||||
h.a.Equal(module.NamespaceID, r.NamespaceID)
|
||||
h.a.Equal(h.cUser.ID, r.OwnedBy)
|
||||
|
||||
h.a.NotEqual(time.Time{}, r.CreatedAt)
|
||||
h.a.Equal(h.cUser.ID, r.CreatedBy)
|
||||
|
||||
h.a.NotNil(r.UpdatedAt)
|
||||
h.a.Equal(h.cUser.ID, r.UpdatedBy)
|
||||
|
||||
h.a.Nil(r.DeletedAt)
|
||||
h.a.Equal(uint64(0), r.DeletedBy)
|
||||
|
||||
h.a.Equal("me+updated@test.tld", r.Values.Get("email", 0).Value)
|
||||
h.a.Equal("Me updated", r.Values.Get("name", 0).Value)
|
||||
h.a.Equal("43", r.Values.Get("a_number", 0).Value)
|
||||
}
|
||||
|
||||
func Test_dal_crud_compose_record_delete(t *testing.T) {
|
||||
t.Skip("needs refactoring")
|
||||
|
||||
h := newHelperT(t)
|
||||
defer h.cleanupDal()
|
||||
|
||||
ctx := h.secCtx()
|
||||
|
||||
helpers.AllowMeModuleCRUD(h)
|
||||
helpers.AllowMeRecordCRUD(h)
|
||||
|
||||
ns := h.createNamespace("test")
|
||||
module := createModuleFromGenerics(ctx, t, "ok_module.json", ns.ID, &types.ModuleConfig{
|
||||
DAL: types.ModuleConfigDAL{
|
||||
ConnectionID: 0,
|
||||
},
|
||||
})
|
||||
|
||||
record := createRecordFromGenerics(ctx, t, "ok_record.json", ns.ID, module.ID)
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/compose/namespace/%d/module/%d/record/%d", ns.ID, module.ID, record.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
rsp := h.apiInit().
|
||||
Get(fmt.Sprintf("/compose/namespace/%d/module/%d/record/%d", ns.ID, module.ID, record.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
|
||||
aux := composeRecordRestRsp{}
|
||||
dd := json.NewDecoder(rsp.Response.Body)
|
||||
h.a.NoError(dd.Decode(&aux))
|
||||
|
||||
r := aux.Response
|
||||
h.a.NotEqual(uint64(0), r.ID)
|
||||
h.a.Equal(module.ID, r.ModuleID)
|
||||
h.a.Equal(module.NamespaceID, r.NamespaceID)
|
||||
h.a.Equal(h.cUser.ID, r.OwnedBy)
|
||||
|
||||
h.a.NotEqual(time.Time{}, r.CreatedAt)
|
||||
h.a.Equal(h.cUser.ID, r.CreatedBy)
|
||||
|
||||
h.a.Nil(r.UpdatedAt)
|
||||
h.a.Equal(uint64(0), r.UpdatedBy)
|
||||
|
||||
h.a.NotNil(r.DeletedAt)
|
||||
h.a.Equal(h.cUser.ID, r.DeletedBy)
|
||||
}
|
||||
189
server/tests/dal/dal_crud_issues_compose_module_test.go
Normal file
189
server/tests/dal/dal_crud_issues_compose_module_test.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package dal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
jsonpath "github.com/steinfletcher/apitest-jsonpath"
|
||||
)
|
||||
|
||||
func Test_dal_crud_issues_compose_module_missing_sensitivity(t *testing.T) {
|
||||
t.Skip("needs refactoring")
|
||||
|
||||
h := newHelperT(t)
|
||||
defer h.cleanupDal()
|
||||
|
||||
helpers.AllowMeModuleCRUD(h)
|
||||
ns := h.createNamespace("test")
|
||||
aux := &composeModuleRestRsp{}
|
||||
|
||||
rsp := h.apiInit().
|
||||
Post(fmt.Sprintf("/compose/namespace/%d/module/", ns.ID)).
|
||||
Body(loadRequestFromGenerics(t, "nok_module_missing_sensitivity_level.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.moduleID")).
|
||||
Assert(jsonpath.Len("$.response.issues", 1)).
|
||||
End()
|
||||
|
||||
dd := json.NewDecoder(rsp.Response.Body)
|
||||
h.a.NoError(dd.Decode(&aux))
|
||||
|
||||
rsp = h.apiInit().
|
||||
Post(fmt.Sprintf("/compose/namespace/%d/module/%d", ns.ID, aux.Response.ID)).
|
||||
Body(loadRequestFromGenerics(t, "ok_module.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.moduleID")).
|
||||
Assert(jsonpath.NotPresent("$.response.issues")).
|
||||
End()
|
||||
|
||||
rsp = h.apiInit().
|
||||
Post(fmt.Sprintf("/compose/namespace/%d/module/%d", ns.ID, aux.Response.ID)).
|
||||
Body(loadRequestFromGenerics(t, "nok_module_missing_sensitivity_level.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.moduleID")).
|
||||
Assert(jsonpath.Len("$.response.issues", 1)).
|
||||
End()
|
||||
|
||||
rsp = h.apiInit().
|
||||
Delete(fmt.Sprintf("/compose/namespace/%d/module/%d", ns.ID, aux.Response.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.success.message")).
|
||||
End()
|
||||
}
|
||||
|
||||
func Test_dal_crud_issues_compose_module_field_missing_sensitivity(t *testing.T) {
|
||||
t.Skip("needs refactoring")
|
||||
|
||||
h := newHelperT(t)
|
||||
defer h.cleanupDal()
|
||||
|
||||
helpers.AllowMeModuleCRUD(h)
|
||||
ns := h.createNamespace("test")
|
||||
aux := &composeModuleRestRsp{}
|
||||
|
||||
rsp := h.apiInit().
|
||||
Post(fmt.Sprintf("/compose/namespace/%d/module/", ns.ID)).
|
||||
Body(loadRequestFromGenerics(t, "nok_module_missing_field_sensitivity_level.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.moduleID")).
|
||||
Assert(jsonpath.Len("$.response.issues", 1)).
|
||||
End()
|
||||
|
||||
dd := json.NewDecoder(rsp.Response.Body)
|
||||
h.a.NoError(dd.Decode(&aux))
|
||||
rsp = h.apiInit().
|
||||
Post(fmt.Sprintf("/compose/namespace/%d/module/%d", ns.ID, aux.Response.ID)).
|
||||
Body(loadRequestFromGenerics(t, "ok_module.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.moduleID")).
|
||||
Assert(jsonpath.NotPresent("$.response.issues")).
|
||||
End()
|
||||
|
||||
rsp = h.apiInit().
|
||||
Post(fmt.Sprintf("/compose/namespace/%d/module/%d", ns.ID, aux.Response.ID)).
|
||||
Body(loadRequestFromGenerics(t, "nok_module_missing_field_sensitivity_level.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.moduleID")).
|
||||
Assert(jsonpath.Len("$.response.issues", 1)).
|
||||
End()
|
||||
|
||||
rsp = h.apiInit().
|
||||
Delete(fmt.Sprintf("/compose/namespace/%d/module/%d", ns.ID, aux.Response.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.success.message")).
|
||||
End()
|
||||
}
|
||||
|
||||
func Test_dal_crud_issues_compose_module_nok_connection(t *testing.T) {
|
||||
t.Skip("needs refactoring")
|
||||
|
||||
h := newHelperT(t)
|
||||
defer h.cleanupDal()
|
||||
|
||||
helpers.AllowMeModuleCRUD(h)
|
||||
helpers.AllowMeDalConnectionCRUD(h)
|
||||
|
||||
ns := h.createNamespace("test")
|
||||
conn := createConnectionFromGenerics(h.secCtx(), t, "nok_connection_connectivity.json")
|
||||
aux := &composeModuleRestRsp{}
|
||||
|
||||
rsp := h.apiInit().
|
||||
Post(fmt.Sprintf("/compose/namespace/%d/module/", ns.ID)).
|
||||
Body(loadRequestFromScenarioWithConnection(t, "module.json", conn.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.moduleID")).
|
||||
Assert(jsonpath.Len("$.response.issues", 1)).
|
||||
End()
|
||||
|
||||
dd := json.NewDecoder(rsp.Response.Body)
|
||||
h.a.NoError(dd.Decode(&aux))
|
||||
|
||||
h.apiInit().
|
||||
Put(fmt.Sprintf("/system/dal/connections/%d", conn.ID)).
|
||||
Body(loadRequestFromGenerics(t, "ok_connection.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.connectionID")).
|
||||
Assert(jsonpath.NotPresent("$.response.issues")).
|
||||
End()
|
||||
|
||||
rsp = h.apiInit().
|
||||
Post(fmt.Sprintf("/compose/namespace/%d/module/%d", ns.ID, aux.Response.ID)).
|
||||
Body(loadRequestFromScenarioWithConnection(t, "module.json", conn.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.moduleID")).
|
||||
Assert(jsonpath.NotPresent("$.response.issues")).
|
||||
End()
|
||||
|
||||
rsp = h.apiInit().
|
||||
Delete(fmt.Sprintf("/compose/namespace/%d/module/%d", ns.ID, aux.Response.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
}
|
||||
100
server/tests/dal/dal_crud_issues_compose_record_test.go
Normal file
100
server/tests/dal/dal_crud_issues_compose_record_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package dal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
)
|
||||
|
||||
func Test_dal_crud_issues_compose_record_nok_connection(t *testing.T) {
|
||||
t.Skip("needs refactoring")
|
||||
|
||||
h := newHelperT(t)
|
||||
defer h.cleanupDal()
|
||||
|
||||
helpers.AllowMeDalConnectionCRUD(h)
|
||||
helpers.AllowMeModuleCRUD(h)
|
||||
helpers.AllowMeRecordCRUD(h)
|
||||
|
||||
ns := h.createNamespace("test")
|
||||
|
||||
connection := createConnectionFromGenerics(h.secCtx(), t, "nok_connection_connectivity.json")
|
||||
module := createModuleFromGenerics(h.secCtx(), t, "ok_module.json", ns.ID, &types.ModuleConfig{
|
||||
DAL: types.ModuleConfigDAL{
|
||||
ConnectionID: connection.ID,
|
||||
},
|
||||
})
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/compose/namespace/%d/module/%d/record/", ns.ID, module.ID)).
|
||||
Body(loadRequestFromGenerics(t, "ok_record.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertErrorP(fmt.Sprintf("connection %d has issues", connection.ID))).
|
||||
End()
|
||||
}
|
||||
|
||||
func Test_dal_crud_issues_compose_record_nok_model(t *testing.T) {
|
||||
t.Skip("needs refactoring")
|
||||
|
||||
h := newHelperT(t)
|
||||
defer h.cleanupDal()
|
||||
|
||||
helpers.AllowMeDalConnectionCRUD(h)
|
||||
helpers.AllowMeModuleCRUD(h)
|
||||
helpers.AllowMeRecordCRUD(h)
|
||||
|
||||
ns := h.createNamespace("test")
|
||||
|
||||
connection := createConnectionFromGenerics(h.secCtx(), t, "ok_connection.json")
|
||||
module := createModuleFromGenerics(h.secCtx(), t, "nok_module_sensitivity_level.json", ns.ID, &types.ModuleConfig{
|
||||
DAL: types.ModuleConfigDAL{
|
||||
ConnectionID: connection.ID,
|
||||
},
|
||||
})
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/compose/namespace/%d/module/%d/record/", ns.ID, module.ID)).
|
||||
Body(loadRequestFromGenerics(t, "ok_record.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertErrorP(fmt.Sprintf("model %d has issues", module.ID))).
|
||||
End()
|
||||
}
|
||||
|
||||
func Test_dal_crud_issues_compose_record_ok(t *testing.T) {
|
||||
t.Skip("needs refactoring")
|
||||
|
||||
h := newHelperT(t)
|
||||
defer h.cleanupDal()
|
||||
|
||||
helpers.AllowMeDalConnectionCRUD(h)
|
||||
helpers.AllowMeModuleCRUD(h)
|
||||
helpers.AllowMeRecordCRUD(h)
|
||||
|
||||
ns := h.createNamespace("test")
|
||||
|
||||
connection := createConnectionFromGenerics(h.secCtx(), t, "ok_connection.json")
|
||||
module := createModuleFromGenerics(h.secCtx(), t, "ok_module.json", ns.ID, &types.ModuleConfig{
|
||||
DAL: types.ModuleConfigDAL{
|
||||
ConnectionID: connection.ID,
|
||||
},
|
||||
})
|
||||
|
||||
h.apiInit().
|
||||
Post(fmt.Sprintf("/compose/namespace/%d/module/%d/record/", ns.ID, module.ID)).
|
||||
Body(loadRequestFromGenerics(t, "ok_record.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
End()
|
||||
}
|
||||
129
server/tests/dal/dal_crud_issues_connection_test.go
Normal file
129
server/tests/dal/dal_crud_issues_connection_test.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package dal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/tests/helpers"
|
||||
jsonpath "github.com/steinfletcher/apitest-jsonpath"
|
||||
)
|
||||
|
||||
func Test_dal_crud_issues_connection_connectivity(t *testing.T) {
|
||||
t.Skip("needs refactoring")
|
||||
|
||||
h := newHelperT(t)
|
||||
defer h.cleanupDal()
|
||||
|
||||
helpers.AllowMeDalConnectionCRUD(h)
|
||||
|
||||
aux := &dalConnectionRestRsp{}
|
||||
|
||||
rsp := h.apiInit().
|
||||
Post("/system/dal/connections/").
|
||||
Body(loadRequestFromGenerics(t, "nok_connection_connectivity.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.connectionID")).
|
||||
Assert(jsonpath.Len("$.response.issues", 1)).
|
||||
End()
|
||||
|
||||
dd := json.NewDecoder(rsp.Response.Body)
|
||||
h.a.NoError(dd.Decode(&aux))
|
||||
|
||||
h.apiInit().
|
||||
Put(fmt.Sprintf("/system/dal/connections/%d", aux.Response.ID)).
|
||||
Body(loadRequestFromGenerics(t, "ok_connection.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.connectionID")).
|
||||
Assert(jsonpath.NotPresent("$.response.issues")).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
Put(fmt.Sprintf("/system/dal/connections/%d", aux.Response.ID)).
|
||||
Body(loadRequestFromGenerics(t, "nok_connection_connectivity.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.connectionID")).
|
||||
Assert(jsonpath.Len("$.response.issues", 1)).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/system/dal/connections/%d", aux.Response.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.success.message")).
|
||||
End()
|
||||
}
|
||||
|
||||
func Test_dal_crud_issues_connection_sensitivity(t *testing.T) {
|
||||
t.Skip("needs refactoring")
|
||||
|
||||
h := newHelperT(t)
|
||||
defer h.cleanupDal()
|
||||
|
||||
helpers.AllowMeDalConnectionCRUD(h)
|
||||
|
||||
aux := &dalConnectionRestRsp{}
|
||||
|
||||
rsp := h.apiInit().
|
||||
Post("/system/dal/connections/").
|
||||
Body(loadRequestFromGenerics(t, "nok_connection_sensitivity.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.connectionID")).
|
||||
Assert(jsonpath.Len("$.response.issues", 1)).
|
||||
End()
|
||||
|
||||
dd := json.NewDecoder(rsp.Response.Body)
|
||||
h.a.NoError(dd.Decode(&aux))
|
||||
|
||||
h.apiInit().
|
||||
Put(fmt.Sprintf("/system/dal/connections/%d", aux.Response.ID)).
|
||||
Body(loadRequestFromGenerics(t, "ok_connection.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.connectionID")).
|
||||
Assert(jsonpath.NotPresent("$.response.issues")).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
Put(fmt.Sprintf("/system/dal/connections/%d", aux.Response.ID)).
|
||||
Body(loadRequestFromGenerics(t, "nok_connection_sensitivity.json")).
|
||||
Header("Accept", "application/json").
|
||||
Header("Content-Type", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.response.connectionID")).
|
||||
Assert(jsonpath.Len("$.response.issues", 1)).
|
||||
End()
|
||||
|
||||
h.apiInit().
|
||||
Delete(fmt.Sprintf("/system/dal/connections/%d", aux.Response.ID)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present("$.success.message")).
|
||||
End()
|
||||
}
|
||||
359
server/tests/dal/dal_utils_test.go
Normal file
359
server/tests/dal/dal_utils_test.go
Normal file
@@ -0,0 +1,359 @@
|
||||
package dal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
composeService "github.com/cortezaproject/corteza/server/compose/service"
|
||||
composeTypes "github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/server/pkg/dal"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/cortezaproject/corteza/server/system/service"
|
||||
"github.com/cortezaproject/corteza/server/system/types"
|
||||
"github.com/cortezaproject/corteza/server/tests/dal/setup/mysql"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
driver struct {
|
||||
name string
|
||||
dsn string
|
||||
setup func(ctx context.Context, t wrapTest, dsn string)
|
||||
}
|
||||
|
||||
dalConnectionRestRsp struct {
|
||||
Response *types.DalConnection
|
||||
}
|
||||
|
||||
composeModuleRestRsp struct {
|
||||
Response *composeTypes.Module
|
||||
}
|
||||
|
||||
composeRecordRestRsp struct {
|
||||
Response *composeTypes.Record
|
||||
}
|
||||
composeRecordSearchRestRsp struct {
|
||||
Response struct {
|
||||
Set composeTypes.RecordSet
|
||||
}
|
||||
}
|
||||
|
||||
wrapTest interface {
|
||||
require.TestingT
|
||||
Name() string
|
||||
}
|
||||
|
||||
modelMetaMaker func(ident string) *dal.Model
|
||||
attributeMaker func() dal.AttributeSet
|
||||
)
|
||||
|
||||
const (
|
||||
sysID = "ID"
|
||||
sysNamespaceID = "namespaceID"
|
||||
sysModuleID = "moduleID"
|
||||
sysCreatedAt = "createdAt"
|
||||
sysCreatedBy = "createdBy"
|
||||
sysUpdatedAt = "updatedAt"
|
||||
sysUpdatedBy = "updatedBy"
|
||||
sysDeletedAt = "deletedAt"
|
||||
sysDeletedBy = "deletedBy"
|
||||
sysOwnedBy = "ownedBy"
|
||||
|
||||
colSysID = "id"
|
||||
colSysNamespaceID = "rel_namespace"
|
||||
colSysModuleID = "rel_module"
|
||||
colSysCreatedAt = "created_at"
|
||||
colSysCreatedBy = "created_by"
|
||||
colSysUpdatedAt = "updated_at"
|
||||
colSysUpdatedBy = "updated_by"
|
||||
colSysDeletedAt = "deleted_at"
|
||||
colSysDeletedBy = "deleted_by"
|
||||
colSysOwnedBy = "owned_by"
|
||||
)
|
||||
|
||||
func (h helper) cleanupDal() {
|
||||
ds := service.DefaultStore
|
||||
ctx := context.Background()
|
||||
|
||||
dd := dal.Service()
|
||||
models, err := dd.SearchModels(ctx)
|
||||
h.a.NoError(err)
|
||||
for _, model := range models {
|
||||
dd.Truncate(ctx, model.ToFilter(), nil)
|
||||
}
|
||||
h.a.NoError(store.TruncateComposeModuleFields(ctx, ds))
|
||||
h.a.NoError(store.TruncateComposeModules(ctx, ds))
|
||||
h.a.NoError(store.TruncateComposeNamespaces(ctx, ds))
|
||||
h.a.NoError(store.TruncateDalSensitivityLevels(ctx, ds))
|
||||
|
||||
cc, _, err := store.SearchDalConnections(ctx, ds, types.DalConnectionFilter{})
|
||||
h.a.NoError(err)
|
||||
for _, c := range cc {
|
||||
if c.Type == types.DalPrimaryConnectionResourceType {
|
||||
continue
|
||||
}
|
||||
h.a.NoError(store.DeleteDalConnectionByID(ctx, ds, c.ID))
|
||||
}
|
||||
dd.Purge(ctx)
|
||||
|
||||
h.a.NoError(service.DefaultDalConnection.ReloadConnections(ctx))
|
||||
h.a.NoError(composeService.DefaultModule.ReloadDALModels(ctx))
|
||||
}
|
||||
|
||||
func initSvc(ctx context.Context, d driver) (dal.FullService, error) {
|
||||
c := makeConnectionDefinition(d.dsn)
|
||||
|
||||
cm := dal.ConnectionConfig{
|
||||
ModelIdent: c.Config.DAL.ModelIdent,
|
||||
SensitivityLevelID: c.Config.Privacy.SensitivityLevelID,
|
||||
Label: c.Handle,
|
||||
}
|
||||
|
||||
svc, err := dal.New(zap.NewNop(), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = svc.ReplaceConnection(
|
||||
ctx,
|
||||
dal.MakeConnection(
|
||||
c.ID,
|
||||
nil,
|
||||
dal.ConnectionParams{
|
||||
Type: "corteza::dal:connection:dsn",
|
||||
Params: map[string]any{
|
||||
"dsn": d.dsn,
|
||||
},
|
||||
},
|
||||
cm,
|
||||
),
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return svc, err
|
||||
}
|
||||
|
||||
func setup(t *testing.T) (ctx context.Context, h helper, log *zap.Logger) {
|
||||
log = zap.NewNop()
|
||||
|
||||
h = newHelperT(t)
|
||||
|
||||
u := &types.User{
|
||||
ID: id.Next(),
|
||||
}
|
||||
u.SetRoles(auth.BypassRoles().IDs()...)
|
||||
|
||||
ctx = auth.SetIdentityToContext(context.Background(), u)
|
||||
|
||||
return ctx, h, log
|
||||
}
|
||||
|
||||
func setupBench(b *testing.B) (ctx context.Context, h helper, log *zap.Logger) {
|
||||
log = zap.NewNop()
|
||||
|
||||
h = newHelperB(b)
|
||||
|
||||
u := &types.User{
|
||||
ID: id.Next(),
|
||||
}
|
||||
u.SetRoles(auth.BypassRoles().IDs()...)
|
||||
|
||||
ctx = auth.SetIdentityToContext(context.Background(), u)
|
||||
|
||||
return ctx, h, log
|
||||
}
|
||||
|
||||
func bootstrap(rootTest *testing.T, run func(context.Context, *testing.T, helper, dal.FullService)) {
|
||||
var (
|
||||
ctx, h, log = setup(rootTest)
|
||||
drivers = collectDrivers()
|
||||
)
|
||||
_ = log
|
||||
|
||||
for _, driver := range drivers {
|
||||
rootTest.Run(driver.name, func(t *testing.T) {
|
||||
if driver.dsn == "" {
|
||||
t.Skip("DSN for DAL test not set")
|
||||
}
|
||||
|
||||
svc, err := initSvc(ctx, driver)
|
||||
require.NoError(t, err)
|
||||
|
||||
driver.setup(ctx, rootTest, driver.dsn)
|
||||
|
||||
run(ctx, t, h, svc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func bootstrapWithModel(rootTest *testing.T, ident string, run func(context.Context, *testing.T, helper, dal.FullService, *dal.Model)) {
|
||||
var (
|
||||
ctx, h, log = setup(rootTest)
|
||||
drivers = collectDrivers()
|
||||
)
|
||||
_ = log
|
||||
|
||||
for _, driver := range drivers {
|
||||
rootTest.Run(driver.name, func(t *testing.T) {
|
||||
if driver.dsn == "" {
|
||||
t.Skip("DSN for DAL test not set")
|
||||
}
|
||||
|
||||
svc, err := initSvc(ctx, driver)
|
||||
require.NoError(t, err)
|
||||
|
||||
model := buildModel(ident, basicModelMeta, fullPartitionedSysAttributes)
|
||||
|
||||
driver.setup(ctx, rootTest, driver.dsn)
|
||||
|
||||
_, err = svc.ReplaceModel(ctx, nil, model)
|
||||
require.NoError(t, err)
|
||||
|
||||
run(ctx, t, h, svc, model)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func bootstrapBenchmark(rootTest *testing.B, run func(context.Context, *testing.B, helper, dal.FullService)) {
|
||||
var (
|
||||
ctx, h, log = setupBench(rootTest)
|
||||
drivers = collectDrivers()
|
||||
)
|
||||
_ = log
|
||||
|
||||
for _, driver := range drivers {
|
||||
rootTest.Run(driver.name, func(b *testing.B) {
|
||||
if driver.dsn == "" {
|
||||
b.Skip("DSN for DAL test not set")
|
||||
}
|
||||
|
||||
svc, err := initSvc(ctx, driver)
|
||||
require.NoError(b, err)
|
||||
|
||||
driver.setup(ctx, rootTest, driver.dsn)
|
||||
|
||||
run(ctx, b, h, svc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func collectDrivers() []driver {
|
||||
return []driver{
|
||||
// {
|
||||
// name: "sqlite",
|
||||
// dsn: "sqlite3+debug://file::memory:?cache=shared&mode=memory",
|
||||
// setup: func(ctx context.Context, t *testing.T, dsn string) {
|
||||
// conn, err := mysql.Setup(ctx, dsn)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// _, err = conn.Exec(loadSetupSource(t, "sqlite.sql"))
|
||||
// require.NoError(t, err)
|
||||
// },
|
||||
// },
|
||||
{
|
||||
name: "mysql",
|
||||
dsn: os.Getenv("DAL_TEST_DSN_MYSQL"),
|
||||
setup: func(ctx context.Context, t wrapTest, dsn string) {
|
||||
conn, err := mysql.Setup(ctx, dsn)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = conn.Exec(loadScenarioSources(t, "mysql", "sql"))
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
// {
|
||||
// name: "postgres",
|
||||
// dsn: os.Getenv("DAL_TEST_DSN_POSTGRES"),
|
||||
// setup: func(ctx context.Context, t *testing.T, dsn string) {
|
||||
// conn, err := mysql.Setup(ctx, dsn)
|
||||
// require.NoError(t, err)
|
||||
|
||||
// _, err = conn.Exec(loadSetupSource(t, "postgres.sql"))
|
||||
// require.NoError(t, err)
|
||||
// },
|
||||
// },
|
||||
}
|
||||
}
|
||||
|
||||
func buildModel(ident string, mm modelMetaMaker, am attributeMaker, aa ...*dal.Attribute) *dal.Model {
|
||||
out := mm(ident)
|
||||
out.Attributes = aa
|
||||
out.Attributes = append(out.Attributes, am()...)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func basicModelMeta(ident string) *dal.Model {
|
||||
return &dal.Model{
|
||||
ConnectionID: 0,
|
||||
Ident: ident,
|
||||
Resource: fmt.Sprintf("testing-resource/%s", ident),
|
||||
ResourceID: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func fullSysAttributes() dal.AttributeSet {
|
||||
return dal.AttributeSet{
|
||||
dal.PrimaryAttribute(sysID, &dal.CodecAlias{Ident: colSysID}),
|
||||
|
||||
dal.FullAttribute(sysModuleID, &dal.TypeID{}, &dal.CodecAlias{Ident: colSysModuleID}),
|
||||
dal.FullAttribute(sysNamespaceID, &dal.TypeID{}, &dal.CodecAlias{Ident: colSysNamespaceID}),
|
||||
|
||||
dal.FullAttribute(sysOwnedBy, &dal.TypeID{}, &dal.CodecAlias{Ident: colSysOwnedBy}),
|
||||
|
||||
dal.FullAttribute(sysCreatedAt, &dal.TypeTimestamp{}, &dal.CodecAlias{Ident: colSysCreatedAt}),
|
||||
dal.FullAttribute(sysCreatedBy, &dal.TypeID{}, &dal.CodecAlias{Ident: colSysCreatedBy}),
|
||||
|
||||
dal.FullAttribute(sysUpdatedAt, &dal.TypeTimestamp{}, &dal.CodecAlias{Ident: colSysUpdatedAt}),
|
||||
dal.FullAttribute(sysUpdatedBy, &dal.TypeID{}, &dal.CodecAlias{Ident: colSysUpdatedBy}),
|
||||
|
||||
dal.FullAttribute(sysDeletedAt, &dal.TypeTimestamp{}, &dal.CodecAlias{Ident: colSysDeletedAt}),
|
||||
dal.FullAttribute(sysDeletedBy, &dal.TypeID{}, &dal.CodecAlias{Ident: colSysDeletedBy}),
|
||||
}
|
||||
}
|
||||
|
||||
func fullPartitionedSysAttributes() dal.AttributeSet {
|
||||
return dal.AttributeSet{
|
||||
dal.PrimaryAttribute(sysID, &dal.CodecAlias{Ident: colSysID}),
|
||||
|
||||
// mod and ns omitted here
|
||||
|
||||
dal.FullAttribute(sysOwnedBy, &dal.TypeID{}, &dal.CodecAlias{Ident: colSysOwnedBy}),
|
||||
|
||||
dal.FullAttribute(sysCreatedAt, &dal.TypeTimestamp{}, &dal.CodecAlias{Ident: colSysCreatedAt}),
|
||||
dal.FullAttribute(sysCreatedBy, &dal.TypeID{}, &dal.CodecAlias{Ident: colSysCreatedBy}),
|
||||
|
||||
dal.FullAttribute(sysUpdatedAt, &dal.TypeTimestamp{}, &dal.CodecAlias{Ident: colSysUpdatedAt}),
|
||||
dal.FullAttribute(sysUpdatedBy, &dal.TypeID{}, &dal.CodecAlias{Ident: colSysUpdatedBy}),
|
||||
|
||||
dal.FullAttribute(sysDeletedAt, &dal.TypeTimestamp{}, &dal.CodecAlias{Ident: colSysDeletedAt}),
|
||||
dal.FullAttribute(sysDeletedBy, &dal.TypeID{}, &dal.CodecAlias{Ident: colSysDeletedBy}),
|
||||
}
|
||||
}
|
||||
|
||||
func drain(ctx context.Context, i dal.Iterator) (rr []*composeTypes.Record, err error) {
|
||||
var r *composeTypes.Record
|
||||
rr = make([]*composeTypes.Record, 0, 100)
|
||||
for i.Next(ctx) {
|
||||
if i.Err() != nil {
|
||||
return nil, i.Err()
|
||||
}
|
||||
|
||||
r = new(composeTypes.Record)
|
||||
if err = i.Scan(r); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rr = append(rr, r)
|
||||
}
|
||||
|
||||
return rr, i.Err()
|
||||
}
|
||||
451
server/tests/dal/main_test.go
Normal file
451
server/tests/dal/main_test.go
Normal file
@@ -0,0 +1,451 @@
|
||||
package dal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
"github.com/cortezaproject/corteza/server/pkg/rbac"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/steinfletcher/apitest"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/app"
|
||||
"github.com/cortezaproject/corteza/server/auth/handlers"
|
||||
"github.com/cortezaproject/corteza/server/auth/request"
|
||||
composeRest "github.com/cortezaproject/corteza/server/compose/rest"
|
||||
composeService "github.com/cortezaproject/corteza/server/compose/service"
|
||||
composeTypes "github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api/server"
|
||||
"github.com/cortezaproject/corteza/server/pkg/cli"
|
||||
"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/store/adapters/rdbms/drivers/sqlite"
|
||||
"github.com/cortezaproject/corteza/server/system/rest"
|
||||
"github.com/cortezaproject/corteza/server/system/service"
|
||||
"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/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type (
|
||||
helper struct {
|
||||
t *testing.T
|
||||
b *testing.B
|
||||
a *require.Assertions
|
||||
|
||||
cUser *types.User
|
||||
roleID uint64
|
||||
token []byte
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
testApp *app.CortezaApp
|
||||
r chi.Router
|
||||
testUser *types.User
|
||||
hh *handlers.AuthHandlers
|
||||
)
|
||||
|
||||
func init() {
|
||||
helpers.RecursiveDotEnvLoad()
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
InitTestApp()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func InitTestApp() {
|
||||
var sm *request.SessionManager
|
||||
|
||||
if testApp == nil {
|
||||
ctx := cli.Context()
|
||||
|
||||
testApp = helpers.NewIntegrationTestApp(ctx, func(app *app.CortezaApp) (err error) {
|
||||
service.CurrentSettings.Auth.External.Enabled = true
|
||||
service.DefaultObjectStore, err = plain.NewWithAfero(afero.NewMemMapFs(), "test")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service.DefaultStore, err = sqlite.ConnectInMemory(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Tests should be executed w/o any locales
|
||||
locale.SetGlobal(locale.Static(&locale.Language{Tag: language.Und}))
|
||||
|
||||
sm = request.NewSessionManager(service.DefaultStore, app.Opt.Auth, service.DefaultLogger)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := store.TruncateUsers(context.Background(), service.DefaultStore); err != nil {
|
||||
panic(fmt.Errorf("could not cleanup users: %v", err))
|
||||
}
|
||||
|
||||
if err := testApp.Activate(ctx); err != nil {
|
||||
panic(fmt.Errorf("could not activate corteza: %v", err))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
hh = &handlers.AuthHandlers{
|
||||
Log: zap.NewNop(),
|
||||
AuthService: service.DefaultAuth,
|
||||
SessionManager: sm,
|
||||
}
|
||||
|
||||
if r == nil {
|
||||
r = chi.NewRouter()
|
||||
r.Use(server.BaseMiddleware(false, logger.Default())...)
|
||||
|
||||
helpers.BindAuthMiddleware(r)
|
||||
r.Route("/system", func(r chi.Router) {
|
||||
r.Group(rest.MountRoutes())
|
||||
})
|
||||
r.Route("/compose", func(r chi.Router) {
|
||||
r.Group(composeRest.MountRoutes())
|
||||
})
|
||||
hh.MountHttpRoutes(r)
|
||||
}
|
||||
}
|
||||
|
||||
func newHelperT(t *testing.T) helper {
|
||||
h := newHelper(t, require.New(t))
|
||||
h.t = t
|
||||
return h
|
||||
}
|
||||
|
||||
func newHelperB(b *testing.B) helper {
|
||||
h := newHelper(b, require.New(b))
|
||||
h.b = b
|
||||
return h
|
||||
}
|
||||
|
||||
func newHelper(_ require.TestingT, a *require.Assertions) helper {
|
||||
var (
|
||||
h = helper{
|
||||
a: a,
|
||||
roleID: id.Next(),
|
||||
}
|
||||
|
||||
ctx = context.Background()
|
||||
)
|
||||
|
||||
if testUser == nil {
|
||||
testUser = &types.User{
|
||||
Handle: "test_user",
|
||||
Name: "test_user",
|
||||
ID: id.Next(),
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
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(ctx, h.cUser)
|
||||
h.mockPermissionsWithAccess()
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// apiInit 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) MyRole() uint64 {
|
||||
return h.roleID
|
||||
}
|
||||
|
||||
// Returns context w/ security details
|
||||
func (h helper) secCtx() context.Context {
|
||||
return auth.SetIdentityToContext(context.Background(), h.cUser)
|
||||
}
|
||||
|
||||
func (h *helper) identityToHelper(ctx context.Context, u *types.User) {
|
||||
var err error
|
||||
h.cUser = u
|
||||
|
||||
h.token, err = auth.TokenIssuer.Issue(ctx, auth.WithIdentity(h.cUser))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h helper) mockPermissions(rules ...*rbac.Rule) {
|
||||
h.a.NoError(rbac.Global().Grant(
|
||||
// TestService we use does not have any backend storage,
|
||||
context.Background(),
|
||||
rules...,
|
||||
))
|
||||
}
|
||||
|
||||
// Prepends allow access rule for system service for everyone
|
||||
func (h helper) mockPermissionsWithAccess(rules ...*rbac.Rule) {
|
||||
h.mockPermissions(rules...)
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Resource utilities
|
||||
|
||||
func (h helper) createNamespace(name string) *composeTypes.Namespace {
|
||||
ns := &composeTypes.Namespace{Name: name, Slug: name}
|
||||
ns.ID = id.Next()
|
||||
ns.CreatedAt = time.Now()
|
||||
h.a.NoError(store.CreateComposeNamespace(context.Background(), service.DefaultStore, ns))
|
||||
return ns
|
||||
}
|
||||
|
||||
func (h helper) createSensitivityLevel(res *types.DalSensitivityLevel) *types.DalSensitivityLevel {
|
||||
if res.ID == 0 {
|
||||
res.ID = id.Next()
|
||||
}
|
||||
|
||||
if res.CreatedAt.IsZero() {
|
||||
res.CreatedAt = time.Now()
|
||||
}
|
||||
|
||||
h.a.NoError(service.DefaultStore.CreateDalSensitivityLevel(context.Background(), res))
|
||||
h.a.NoError(service.DefaultDalSensitivityLevel.ReloadSensitivityLevels(context.Background(), service.DefaultStore))
|
||||
return res
|
||||
}
|
||||
|
||||
func (h helper) createDalConnection(res *types.DalConnection) *types.DalConnection {
|
||||
if res.ID == 0 {
|
||||
res.ID = id.Next()
|
||||
}
|
||||
|
||||
if res.Meta.Name == "" {
|
||||
res.Meta.Name = "Test Connection"
|
||||
}
|
||||
if res.Handle == "" {
|
||||
res.Handle = "test_connection"
|
||||
}
|
||||
if res.Type == "" {
|
||||
res.Type = types.DalConnectionResourceType
|
||||
}
|
||||
if res.Meta.Ownership == "" {
|
||||
res.Meta.Ownership = "tester"
|
||||
}
|
||||
|
||||
if res.Config.DAL == nil {
|
||||
res.Config.DAL = &types.ConnectionConfigDAL{
|
||||
Type: "corteza::dal:connection:dsn",
|
||||
Params: map[string]any{
|
||||
"dsn": "sqlite3://file::memory:?cache=shared&mode=memory",
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if res.Config.DAL.ModelIdent == "" {
|
||||
res.Config.DAL.ModelIdent = "compose_records"
|
||||
}
|
||||
|
||||
if res.CreatedAt.IsZero() {
|
||||
res.CreatedAt = time.Now()
|
||||
}
|
||||
if res.CreatedBy == 0 {
|
||||
res.CreatedBy = h.cUser.ID
|
||||
}
|
||||
|
||||
h.a.NoError(service.DefaultStore.CreateDalConnection(context.Background(), res))
|
||||
h.a.NoError(service.DefaultDalConnection.ReloadConnections(context.Background()))
|
||||
return res
|
||||
}
|
||||
|
||||
func (h helper) getPrimaryConnection() *types.DalConnection {
|
||||
cc, _, err := store.SearchDalConnections(context.Background(), service.DefaultStore, types.DalConnectionFilter{Type: types.DalPrimaryConnectionResourceType})
|
||||
h.a.NoError(err)
|
||||
|
||||
if len(cc) != 1 {
|
||||
h.a.FailNow("invalid state: no or too many primary connections")
|
||||
}
|
||||
|
||||
return cc[0]
|
||||
}
|
||||
|
||||
func makeConnectionDefinition(dsn string) *types.DalConnection {
|
||||
return &types.DalConnection{
|
||||
ID: id.Next(),
|
||||
Type: types.DalConnectionResourceType,
|
||||
Config: types.ConnectionConfig{
|
||||
DAL: &types.ConnectionConfigDAL{
|
||||
ModelIdent: "compose_record",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Utilities
|
||||
|
||||
// ---
|
||||
|
||||
func loadRequestFromScenario(t *testing.T, name string) string {
|
||||
return loadRequestFrom(t, suiteFromT(t), name)
|
||||
}
|
||||
func loadRequestFromGenerics(t *testing.T, name string) string {
|
||||
return loadRequestFrom(t, "generic", name)
|
||||
}
|
||||
func loadRequestFrom(t *testing.T, suite, name string) string {
|
||||
f, err := os.Open(path.Join("testdata", suite, name))
|
||||
require.NoError(t, err)
|
||||
defer f.Close()
|
||||
|
||||
bb, err := ioutil.ReadAll(f)
|
||||
require.NoError(t, err)
|
||||
|
||||
return string(bb)
|
||||
}
|
||||
func loadRequestFromScenarioWithConnection(t *testing.T, req string, connectionID uint64) string {
|
||||
out := loadRequestFromScenario(t, req)
|
||||
|
||||
aux := &composeTypes.Module{}
|
||||
require.NoError(t, json.Unmarshal([]byte(out), &aux))
|
||||
|
||||
aux.Config.DAL.ConnectionID = connectionID
|
||||
|
||||
a, err := json.Marshal(aux)
|
||||
require.NoError(t, err)
|
||||
|
||||
return string(a)
|
||||
}
|
||||
|
||||
func createRecordFromCase(ctx context.Context, t *testing.T, name string, namespaceID, moduleID uint64) (record *composeTypes.Record) {
|
||||
return createRecordFrom(ctx, t, suiteFromT(t), name, namespaceID, moduleID)
|
||||
}
|
||||
func createRecordFromGenerics(ctx context.Context, t *testing.T, name string, namespaceID, moduleID uint64) (record *composeTypes.Record) {
|
||||
return createRecordFrom(ctx, t, "generic", name, namespaceID, moduleID)
|
||||
}
|
||||
func createRecordFrom(ctx context.Context, t *testing.T, suite, name string, namespaceID, moduleID uint64) (record *composeTypes.Record) {
|
||||
raw := loadRequestFrom(t, suite, name)
|
||||
|
||||
record = &composeTypes.Record{}
|
||||
require.NoError(t, json.Unmarshal([]byte(raw), &record))
|
||||
|
||||
record.NamespaceID = namespaceID
|
||||
record.ModuleID = moduleID
|
||||
|
||||
record, _, err := composeService.DefaultRecord.Create(ctx, record)
|
||||
require.NoError(t, err)
|
||||
|
||||
return record
|
||||
}
|
||||
|
||||
func createModuleFromCase(ctx context.Context, t *testing.T, name string, namespaceID uint64, config *composeTypes.ModuleConfig) (module *composeTypes.Module) {
|
||||
return createModuleFrom(ctx, t, suiteFromT(t), name, namespaceID, config)
|
||||
}
|
||||
func createModuleFromGenerics(ctx context.Context, t *testing.T, name string, namespaceID uint64, config *composeTypes.ModuleConfig) (module *composeTypes.Module) {
|
||||
return createModuleFrom(ctx, t, "generic", name, namespaceID, config)
|
||||
}
|
||||
func createModuleFrom(ctx context.Context, t *testing.T, suite, name string, namespaceID uint64, config *composeTypes.ModuleConfig) (module *composeTypes.Module) {
|
||||
raw := loadRequestFrom(t, suite, name)
|
||||
|
||||
module = &composeTypes.Module{}
|
||||
require.NoError(t, json.Unmarshal([]byte(raw), &module))
|
||||
|
||||
module.NamespaceID = namespaceID
|
||||
|
||||
if config != nil {
|
||||
// let's be careful not to override whole config
|
||||
module.Config.DAL = config.DAL
|
||||
}
|
||||
|
||||
module, err := composeService.DefaultModule.Create(ctx, module)
|
||||
require.NoError(t, err)
|
||||
|
||||
return module
|
||||
}
|
||||
|
||||
func createConnectionFromCase(ctx context.Context, t *testing.T, name string) (connection *types.DalConnection) {
|
||||
return createConnectionFrom(ctx, t, suiteFromT(t), name)
|
||||
}
|
||||
func createConnectionFromGenerics(ctx context.Context, t *testing.T, name string) (connection *types.DalConnection) {
|
||||
return createConnectionFrom(ctx, t, "generic", name)
|
||||
}
|
||||
func createConnectionFrom(ctx context.Context, t *testing.T, suite, name string) (connection *types.DalConnection) {
|
||||
raw := loadRequestFrom(t, suite, name)
|
||||
|
||||
aux := &types.DalConnection{}
|
||||
require.NoError(t, json.Unmarshal([]byte(raw), &aux))
|
||||
|
||||
connection, err := service.DefaultDalConnection.Create(ctx, aux)
|
||||
require.NoError(t, err)
|
||||
|
||||
return connection
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
func suiteFromT(t *testing.T) string {
|
||||
return t.Name()[5:]
|
||||
}
|
||||
|
||||
// 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 loadScenarioSources(t wrapTest, driver, ext string) (src string) {
|
||||
scenarioName := t.Name()[5:]
|
||||
|
||||
src = loadScenarioSource(t, "generic", fmt.Sprintf("_.%s", ext))
|
||||
src += "\n"
|
||||
src += loadScenarioSource(t, "generic", fmt.Sprintf("%s.%s", driver, ext))
|
||||
src += "\n"
|
||||
src += loadScenarioSource(t, scenarioName, fmt.Sprintf("_.%s", ext))
|
||||
src += "\n"
|
||||
src += loadScenarioSource(t, scenarioName, fmt.Sprintf("%s.%s", driver, ext))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func loadScenarioSource(t wrapTest, scenarioName, srcName string) (src string) {
|
||||
f, err := os.Open(path.Join("testdata", scenarioName, srcName))
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return ""
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer f.Close()
|
||||
|
||||
out, err := ioutil.ReadAll(bufio.NewReader(f))
|
||||
require.NoError(t, err)
|
||||
|
||||
return string(out)
|
||||
}
|
||||
1619
server/tests/dal/pagination_sorting_test.go
Normal file
1619
server/tests/dal/pagination_sorting_test.go
Normal file
File diff suppressed because it is too large
Load Diff
23
server/tests/dal/setup/mysql/setup.go
Normal file
23
server/tests/dal/setup/mysql/setup.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza/server/store/adapters/rdbms"
|
||||
"github.com/cortezaproject/corteza/server/store/adapters/rdbms/drivers/mysql"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
func Setup(ctx context.Context, dsn string) (_ *sqlx.DB, err error) {
|
||||
var (
|
||||
cfg *rdbms.ConnConfig
|
||||
)
|
||||
|
||||
cfg, err = mysql.NewConfig(dsn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return rdbms.Connect(ctx, logger.MakeDebugLogger(), cfg)
|
||||
}
|
||||
23
server/tests/dal/setup/postgres/setup.go
Normal file
23
server/tests/dal/setup/postgres/setup.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza/server/store/adapters/rdbms"
|
||||
"github.com/cortezaproject/corteza/server/store/adapters/rdbms/drivers/postgres"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
func Setup(ctx context.Context, dsn string) (_ *sqlx.DB, err error) {
|
||||
var (
|
||||
cfg *rdbms.ConnConfig
|
||||
)
|
||||
|
||||
cfg, err = postgres.NewConfig(dsn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return rdbms.Connect(ctx, logger.MakeDebugLogger(), cfg)
|
||||
}
|
||||
23
server/tests/dal/setup/sqlite/setup.go
Normal file
23
server/tests/dal/setup/sqlite/setup.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza/server/store/adapters/rdbms"
|
||||
"github.com/cortezaproject/corteza/server/store/adapters/rdbms/drivers/sqlite"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
func Setup(ctx context.Context, dsn string) (_ *sqlx.DB, err error) {
|
||||
var (
|
||||
cfg *rdbms.ConnConfig
|
||||
)
|
||||
|
||||
cfg, err = sqlite.NewConfig(dsn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return rdbms.Connect(ctx, logger.MakeDebugLogger(), cfg)
|
||||
}
|
||||
48
server/tests/dal/testdata/dal_crud_connection_update_primary/connection.json
vendored
Normal file
48
server/tests/dal/testdata/dal_crud_connection_update_primary/connection.json
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"handle": "primary_connection",
|
||||
"type": "corteza::system:primary-dal-connection",
|
||||
|
||||
"meta": {
|
||||
"name": "Primary Connection EDITED",
|
||||
"location": {
|
||||
"geometry": {
|
||||
"type": "",
|
||||
"coordinates": null
|
||||
},
|
||||
"properties": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"ownership": ""
|
||||
},
|
||||
|
||||
"config": {
|
||||
"connection": {
|
||||
"type": "corteza::dal:connection:dsn",
|
||||
"params": {
|
||||
"dsn": "sqlite3://file::memory:?cache=shared&mode=memory"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"sensitivityLevelID": "0"
|
||||
},
|
||||
"dal": {
|
||||
"properties": {},
|
||||
"operations": [
|
||||
"corteza::dal:operation:create",
|
||||
"corteza::dal:operation:update",
|
||||
"corteza::dal:operation:search",
|
||||
"corteza::dal:operation:lookup",
|
||||
"corteza::dal:operation:paging",
|
||||
"corteza::dal:operation:stats",
|
||||
"corteza::dal:operation:sorting",
|
||||
"corteza::dal:operation:RBAC"
|
||||
],
|
||||
|
||||
"modelIdent": "compose_record",
|
||||
"attributeIdent": "values",
|
||||
"partitionFormat": "compose_record_{{namespace}}_{{module}}",
|
||||
"partitionIdentValidator": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
34
server/tests/dal/testdata/dal_crud_issues_compose_module_nok_connection/module.json
vendored
Normal file
34
server/tests/dal/testdata/dal_crud_issues_compose_module_nok_connection/module.json
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "internal_non_partitioned",
|
||||
"handle": "internal_non_partitioned",
|
||||
|
||||
"config": {
|
||||
"dal": {
|
||||
"connectionID": "42",
|
||||
"operations": [],
|
||||
"partitioned": false
|
||||
},
|
||||
|
||||
"privacy": {
|
||||
"sensitivityLevelID": "0",
|
||||
"usageDisclosure": "A"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
"fields": [
|
||||
{
|
||||
"name": "f1",
|
||||
"kind": "String",
|
||||
|
||||
"privacy": {
|
||||
"sensitivityLevelID": "0",
|
||||
"usageDisclosure": "A"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "f2",
|
||||
"kind": "String"
|
||||
}
|
||||
]
|
||||
}
|
||||
48
server/tests/dal/testdata/generic/_.sql
vendored
Normal file
48
server/tests/dal/testdata/generic/_.sql
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
DROP TABLE IF EXISTS compose_record;
|
||||
CREATE TABLE `compose_record` (
|
||||
`id` bigint unsigned NOT NULL,
|
||||
`rel_namespace` bigint unsigned NOT NULL,
|
||||
`module_id` bigint unsigned NOT NULL,
|
||||
|
||||
`values` json DEFAULT NULL,
|
||||
|
||||
`owned_by` bigint unsigned NOT NULL,
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime DEFAULT NULL,
|
||||
`deleted_at` datetime DEFAULT NULL,
|
||||
`created_by` bigint unsigned NOT NULL,
|
||||
`updated_by` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`deleted_by` bigint unsigned NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `compose_record_owner` (`owned_by`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
DROP TABLE IF EXISTS compose_record_partitioned;
|
||||
CREATE TABLE `compose_record_partitioned` (
|
||||
`id` bigint unsigned NOT NULL,
|
||||
|
||||
`vID` BIGINT UNSIGNED,
|
||||
`vRef` BIGINT UNSIGNED,
|
||||
`vTimestamp` DATETIME,
|
||||
`vTime` TIME,
|
||||
`vDate` DATE,
|
||||
`vNumber` NUMERIC,
|
||||
`vText` TEXT,
|
||||
`vBoolean_T` BOOLEAN,
|
||||
`vBoolean_F` BOOLEAN,
|
||||
`vEnum` TEXT,
|
||||
`vGeometry` TEXT,
|
||||
`vJSON` TEXT,
|
||||
`vBlob` BLOB,
|
||||
`vUUID` VARCHAR(36),
|
||||
|
||||
`owned_by` bigint unsigned NOT NULL,
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime DEFAULT NULL,
|
||||
`deleted_at` datetime DEFAULT NULL,
|
||||
`created_by` bigint unsigned NOT NULL,
|
||||
`updated_by` bigint unsigned NOT NULL DEFAULT '0',
|
||||
`deleted_by` bigint unsigned NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `compose_record_owner` (`owned_by`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
|
||||
32
server/tests/dal/testdata/generic/nok_connection_connectivity.json
vendored
Normal file
32
server/tests/dal/testdata/generic/nok_connection_connectivity.json
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"handle": "test_connection",
|
||||
"type": "corteza::system:dal-connection",
|
||||
|
||||
"meta": {
|
||||
"name": "Test Connection",
|
||||
"location": {
|
||||
"geometry": {
|
||||
"type": "",
|
||||
"coordinates": null
|
||||
},
|
||||
"properties": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"ownership": ""
|
||||
},
|
||||
|
||||
"config": {
|
||||
"dal": {
|
||||
"modelIdent": "compose_record",
|
||||
|
||||
"type": "corteza::dal:connection:dsn",
|
||||
"params": {
|
||||
"dsn": "NO"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"sensitivityLevelID": "0"
|
||||
}
|
||||
}
|
||||
}
|
||||
32
server/tests/dal/testdata/generic/nok_connection_invalid_type.json
vendored
Normal file
32
server/tests/dal/testdata/generic/nok_connection_invalid_type.json
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"handle": "test_connection",
|
||||
"type": "corteza::system:primary-dal-connection",
|
||||
|
||||
"meta": {
|
||||
"name": "Test Connection",
|
||||
"location": {
|
||||
"geometry": {
|
||||
"type": "",
|
||||
"coordinates": null
|
||||
},
|
||||
"properties": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"ownership": ""
|
||||
},
|
||||
|
||||
"config": {
|
||||
"dal": {
|
||||
"modelIdent": "compose_record",
|
||||
|
||||
"type": "corteza::dal:connection:dsn",
|
||||
"params": {
|
||||
"dsn": "sqlite3://file::memory:?cache=shared&mode=memory"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"sensitivityLevelID": "0"
|
||||
}
|
||||
}
|
||||
}
|
||||
32
server/tests/dal/testdata/generic/nok_connection_sensitivity.json
vendored
Normal file
32
server/tests/dal/testdata/generic/nok_connection_sensitivity.json
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"handle": "test_connection",
|
||||
"type": "corteza::system:dal-connection",
|
||||
|
||||
"meta": {
|
||||
"name": "Test Connection",
|
||||
"location": {
|
||||
"geometry": {
|
||||
"type": "",
|
||||
"coordinates": null
|
||||
},
|
||||
"properties": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"ownership": ""
|
||||
},
|
||||
|
||||
"config": {
|
||||
"dal": {
|
||||
"modelIdent": "compose_record",
|
||||
|
||||
"type": "corteza::dal:connection:dsn",
|
||||
"params": {
|
||||
"dsn": "sqlite3://file::memory:?cache=shared&mode=memory"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"sensitivityLevelID": "42"
|
||||
}
|
||||
}
|
||||
}
|
||||
38
server/tests/dal/testdata/generic/nok_module_missing_field_sensitivity_level.json
vendored
Normal file
38
server/tests/dal/testdata/generic/nok_module_missing_field_sensitivity_level.json
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "internal_non_partitioned",
|
||||
"handle": "internal_non_partitioned",
|
||||
|
||||
"config": {
|
||||
"dal": {
|
||||
"connectionID": "0",
|
||||
"operations": [],
|
||||
"partitioned": false
|
||||
},
|
||||
"privacy": {
|
||||
"sensitivityLevelID": "0",
|
||||
"usageDisclosure": "A"
|
||||
}
|
||||
},
|
||||
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
"kind": "String",
|
||||
|
||||
"config": {
|
||||
"privacy": {
|
||||
"sensitivityLevelID": "42",
|
||||
"usageDisclosure": "A"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"kind": "Email"
|
||||
},
|
||||
{
|
||||
"name": "a_number",
|
||||
"kind": "Number"
|
||||
}
|
||||
]
|
||||
}
|
||||
36
server/tests/dal/testdata/generic/nok_module_missing_sensitivity_level.json
vendored
Normal file
36
server/tests/dal/testdata/generic/nok_module_missing_sensitivity_level.json
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "internal_non_partitioned",
|
||||
"handle": "internal_non_partitioned",
|
||||
|
||||
"config": {
|
||||
"dal": {
|
||||
"connectionID": "0",
|
||||
"operations": [],
|
||||
"partitioned": false
|
||||
},
|
||||
"privacy": {
|
||||
"sensitivityLevelID": "42",
|
||||
"usageDisclosure": "A"
|
||||
}
|
||||
},
|
||||
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
"kind": "String",
|
||||
|
||||
"privacy": {
|
||||
"sensitivityLevelID": "0",
|
||||
"usageDisclosure": "A"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"kind": "Email"
|
||||
},
|
||||
{
|
||||
"name": "a_number",
|
||||
"kind": "Number"
|
||||
}
|
||||
]
|
||||
}
|
||||
37
server/tests/dal/testdata/generic/nok_module_sensitivity_level.json
vendored
Normal file
37
server/tests/dal/testdata/generic/nok_module_sensitivity_level.json
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "internal_non_partitioned",
|
||||
"handle": "internal_non_partitioned",
|
||||
|
||||
"config": {
|
||||
"dal": {
|
||||
"connectionID": "0",
|
||||
"operations": [],
|
||||
"partitioned": false
|
||||
},
|
||||
|
||||
"privacy": {
|
||||
"sensitivityLevelID": "42",
|
||||
"usageDisclosure": "A"
|
||||
}
|
||||
},
|
||||
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
"kind": "String",
|
||||
|
||||
"privacy": {
|
||||
"sensitivityLevelID": "0",
|
||||
"usageDisclosure": "A"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"kind": "Email"
|
||||
},
|
||||
{
|
||||
"name": "a_number",
|
||||
"kind": "Number"
|
||||
}
|
||||
]
|
||||
}
|
||||
32
server/tests/dal/testdata/generic/ok_connection.json
vendored
Normal file
32
server/tests/dal/testdata/generic/ok_connection.json
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"handle": "test_connection",
|
||||
"type": "corteza::system:dal-connection",
|
||||
|
||||
"meta": {
|
||||
"name": "Test Connection",
|
||||
"location": {
|
||||
"geometry": {
|
||||
"type": "",
|
||||
"coordinates": null
|
||||
},
|
||||
"properties": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"ownership": ""
|
||||
},
|
||||
|
||||
"config": {
|
||||
"dal": {
|
||||
"modelIdent": "compose_record",
|
||||
|
||||
"type": "corteza::dal:connection:dsn",
|
||||
"params": {
|
||||
"dsn": "sqlite3://file::memory:?cache=shared&mode=memory"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"sensitivityLevelID": "0"
|
||||
}
|
||||
}
|
||||
}
|
||||
32
server/tests/dal/testdata/generic/ok_connection_update.json
vendored
Normal file
32
server/tests/dal/testdata/generic/ok_connection_update.json
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"handle": "test_connection_edited",
|
||||
"type": "corteza::system:dal-connection",
|
||||
|
||||
"meta": {
|
||||
"name": "Test Connection EDITED",
|
||||
"location": {
|
||||
"geometry": {
|
||||
"type": "",
|
||||
"coordinates": null
|
||||
},
|
||||
"properties": {
|
||||
"name": ""
|
||||
}
|
||||
},
|
||||
"ownership": ""
|
||||
},
|
||||
|
||||
"config": {
|
||||
"dal": {
|
||||
"modelIdent": "compose_record",
|
||||
|
||||
"type": "corteza::dal:connection:dsn",
|
||||
"params": {
|
||||
"dsn": "sqlite3://file::memory:?cache=shared&mode=memory"
|
||||
}
|
||||
},
|
||||
"privacy": {
|
||||
"sensitivityLevelID": "0"
|
||||
}
|
||||
}
|
||||
}
|
||||
31
server/tests/dal/testdata/generic/ok_module.json
vendored
Normal file
31
server/tests/dal/testdata/generic/ok_module.json
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "internal_non_partitioned",
|
||||
"handle": "internal_non_partitioned",
|
||||
|
||||
"config": {
|
||||
"dal": {
|
||||
"connectionID": "0",
|
||||
"operations": [],
|
||||
"partitioned": false
|
||||
},
|
||||
"privacy": {
|
||||
"sensitivityLevelID": "0",
|
||||
"usageDisclosure": "A"
|
||||
}
|
||||
},
|
||||
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
"kind": "String"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"kind": "Email"
|
||||
},
|
||||
{
|
||||
"name": "a_number",
|
||||
"kind": "Number"
|
||||
}
|
||||
]
|
||||
}
|
||||
16
server/tests/dal/testdata/generic/ok_record.json
vendored
Normal file
16
server/tests/dal/testdata/generic/ok_record.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"values": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "Me"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"value": "me@test.tld"
|
||||
},
|
||||
{
|
||||
"name": "a_number",
|
||||
"value": "42"
|
||||
}
|
||||
]
|
||||
}
|
||||
16
server/tests/dal/testdata/generic/ok_record_update.json
vendored
Normal file
16
server/tests/dal/testdata/generic/ok_record_update.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"values": [
|
||||
{
|
||||
"name": "name",
|
||||
"value": "Me updated"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"value": "me+updated@test.tld"
|
||||
},
|
||||
{
|
||||
"name": "a_number",
|
||||
"value": "43"
|
||||
}
|
||||
]
|
||||
}
|
||||
8
server/tests/dal/testdata/generic/ok_sensitivity_level.json
vendored
Normal file
8
server/tests/dal/testdata/generic/ok_sensitivity_level.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"handle": "private",
|
||||
"level": 1,
|
||||
"meta": {
|
||||
"name": "private sensitivity",
|
||||
"description": "data is private to the owner"
|
||||
}
|
||||
}
|
||||
8
server/tests/dal/testdata/generic/ok_sensitivity_level_update.json
vendored
Normal file
8
server/tests/dal/testdata/generic/ok_sensitivity_level_update.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"handle": "private_edited",
|
||||
"level": 1,
|
||||
"meta": {
|
||||
"name": "private sensitivity",
|
||||
"description": "data is private to the owner"
|
||||
}
|
||||
}
|
||||
388
server/tests/envoy/import_export_test.go
Normal file
388
server/tests/envoy/import_export_test.go
Normal file
@@ -0,0 +1,388 @@
|
||||
package envoy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
automationTypes "github.com/cortezaproject/corteza/server/automation/types"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/envoyx"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
systemTypes "github.com/cortezaproject/corteza/server/system/types"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestImportExport(t *testing.T) {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
req = require.New(t)
|
||||
nodes envoyx.NodeSet
|
||||
gg *envoyx.DepGraph
|
||||
err error
|
||||
)
|
||||
|
||||
cleanup(t)
|
||||
|
||||
// The test
|
||||
//
|
||||
// * imports some YAML files
|
||||
// * checks the DB state
|
||||
// * exports the DB into a YAML
|
||||
// * clears the DB
|
||||
// * imports the YAML
|
||||
// * checks the DB state the same way as before
|
||||
//
|
||||
// The above outlined flow allows us to trivially check if the data is both
|
||||
// imported and exported correctly.
|
||||
//
|
||||
// The initial step could also manually populate the DB but the YAML import
|
||||
// is more convenient.
|
||||
|
||||
t.Run("initial import", func(t *testing.T) {
|
||||
t.Run("parse configs", func(t *testing.T) {
|
||||
nodes, _, err = defaultEnvoy.Decode(ctx, envoyx.DecodeParams{
|
||||
Type: envoyx.DecodeTypeURI,
|
||||
Params: map[string]any{
|
||||
"uri": "file://testdata/full",
|
||||
},
|
||||
})
|
||||
req.NoError(err)
|
||||
})
|
||||
|
||||
t.Run("bake", func(t *testing.T) {
|
||||
gg, err = defaultEnvoy.Bake(ctx, envoyx.EncodeParams{
|
||||
Type: envoyx.EncodeTypeStore,
|
||||
Params: map[string]any{
|
||||
"storer": defaultStore,
|
||||
"dal": defaultDal,
|
||||
},
|
||||
}, nil, nodes...)
|
||||
req.NoError(err)
|
||||
})
|
||||
|
||||
t.Run("import into DB", func(t *testing.T) {
|
||||
err = defaultEnvoy.Encode(ctx, envoyx.EncodeParams{
|
||||
Type: envoyx.EncodeTypeStore,
|
||||
Params: map[string]any{
|
||||
"storer": defaultStore,
|
||||
"dal": defaultDal,
|
||||
},
|
||||
}, gg)
|
||||
req.NoError(err)
|
||||
})
|
||||
|
||||
assertFullState(ctx, t, defaultStore, req)
|
||||
})
|
||||
|
||||
// Prepare a temp file where we'll dump the YAML into
|
||||
auxFile, err := os.CreateTemp(os.TempDir(), "*.yaml")
|
||||
req.NoError(err)
|
||||
spew.Dump(auxFile.Name())
|
||||
// defer os.Remove(auxFile.Name())
|
||||
defer auxFile.Close()
|
||||
|
||||
t.Run("export", func(t *testing.T) {
|
||||
t.Run("export from DB", func(t *testing.T) {
|
||||
nodes, _, err = defaultEnvoy.Decode(ctx, envoyx.DecodeParams{
|
||||
Type: envoyx.DecodeTypeStore,
|
||||
Params: map[string]any{
|
||||
"storer": defaultStore,
|
||||
"dal": defaultDal,
|
||||
},
|
||||
Filter: map[string]envoyx.ResourceFilter{
|
||||
types.ChartResourceType: {},
|
||||
types.ModuleResourceType: {},
|
||||
types.NamespaceResourceType: {},
|
||||
types.PageResourceType: {},
|
||||
types.PageLayoutResourceType: {},
|
||||
|
||||
systemTypes.ApplicationResourceType: {},
|
||||
systemTypes.ApigwRouteResourceType: {},
|
||||
systemTypes.AuthClientResourceType: {},
|
||||
systemTypes.QueueResourceType: {},
|
||||
systemTypes.ReportResourceType: {},
|
||||
systemTypes.RoleResourceType: {},
|
||||
systemTypes.TemplateResourceType: {},
|
||||
systemTypes.UserResourceType: {},
|
||||
systemTypes.DalConnectionResourceType: {},
|
||||
systemTypes.DalSensitivityLevelResourceType: {},
|
||||
|
||||
automationTypes.WorkflowResourceType: {},
|
||||
},
|
||||
})
|
||||
req.NoError(err)
|
||||
})
|
||||
|
||||
t.Run("bake", func(t *testing.T) {
|
||||
gg, err = defaultEnvoy.Bake(ctx, envoyx.EncodeParams{
|
||||
Type: envoyx.EncodeTypeStore,
|
||||
Params: map[string]any{
|
||||
"storer": defaultStore,
|
||||
"dal": defaultDal,
|
||||
},
|
||||
}, nil, nodes...)
|
||||
req.NoError(err)
|
||||
})
|
||||
|
||||
t.Run("write file", func(t *testing.T) {
|
||||
err = defaultEnvoy.Encode(ctx, envoyx.EncodeParams{
|
||||
Type: envoyx.EncodeTypeIo,
|
||||
Params: map[string]any{
|
||||
"writer": auxFile,
|
||||
},
|
||||
}, gg)
|
||||
req.NoError(err)
|
||||
})
|
||||
})
|
||||
|
||||
cleanup(t)
|
||||
|
||||
t.Run("second import", func(t *testing.T) {
|
||||
t.Run("yaml parse", func(t *testing.T) {
|
||||
nodes, _, err = defaultEnvoy.Decode(ctx, envoyx.DecodeParams{
|
||||
Type: envoyx.DecodeTypeURI,
|
||||
Params: map[string]any{
|
||||
"uri": fmt.Sprintf("file://%s", auxFile.Name()),
|
||||
},
|
||||
})
|
||||
req.NoError(err)
|
||||
})
|
||||
|
||||
t.Run("bake", func(t *testing.T) {
|
||||
gg, err = defaultEnvoy.Bake(ctx, envoyx.EncodeParams{
|
||||
Type: envoyx.EncodeTypeStore,
|
||||
Params: map[string]any{
|
||||
"storer": defaultStore,
|
||||
"dal": defaultDal,
|
||||
},
|
||||
}, nil, nodes...)
|
||||
req.NoError(err)
|
||||
})
|
||||
|
||||
t.Run("run import", func(t *testing.T) {
|
||||
err = defaultEnvoy.Encode(ctx, envoyx.EncodeParams{
|
||||
Type: envoyx.EncodeTypeStore,
|
||||
Params: map[string]any{
|
||||
"storer": defaultStore,
|
||||
"dal": defaultDal,
|
||||
},
|
||||
}, gg)
|
||||
req.NoError(err)
|
||||
})
|
||||
|
||||
assertFullState(ctx, t, defaultStore, req)
|
||||
})
|
||||
}
|
||||
|
||||
func assertFullState(ctx context.Context, t *testing.T, s store.Storer, req *require.Assertions) {
|
||||
t.Run("check state", func(t *testing.T) {
|
||||
t.Run("corteza::compose", func(t *testing.T) {
|
||||
// Namespaces
|
||||
ns, err := store.LookupComposeNamespaceBySlug(ctx, s, "test_ns_1")
|
||||
req.NoError(err)
|
||||
req.NotNil(ns)
|
||||
|
||||
// Modules
|
||||
modules, _, err := store.SearchComposeModules(ctx, s, types.ModuleFilter{
|
||||
NamespaceID: ns.ID,
|
||||
Sorting: filter.Sorting{
|
||||
Sort: filter.SortExprSet{{Column: "handle", Descending: false}},
|
||||
},
|
||||
})
|
||||
req.NoError(err)
|
||||
|
||||
mod1 := modules.FindByHandle("test_ns_1_mod_1")
|
||||
req.NotNil(mod1)
|
||||
|
||||
mod2 := modules.FindByHandle("test_ns_1_mod_2")
|
||||
req.NotNil(mod2)
|
||||
|
||||
// Fields
|
||||
fields, _, err := store.SearchComposeModuleFields(ctx, s, types.ModuleFieldFilter{
|
||||
ModuleID: []uint64{mod1.ID, mod2.ID},
|
||||
})
|
||||
req.NoError(err)
|
||||
|
||||
assignModuleFields(modules, fields)
|
||||
|
||||
req.Len(mod1.Fields, 2)
|
||||
req.Len(mod2.Fields, 2)
|
||||
|
||||
req.Equal(mod1.ID, mod2.Fields.FindByName("test_ns_1_mod_2_f1").Options.UInt64("moduleID"))
|
||||
req.Equal(mod2.ID, mod2.Fields.FindByName("test_ns_1_mod_2_f2").Options.UInt64("moduleID"))
|
||||
|
||||
// Charts
|
||||
charts, _, err := store.SearchComposeCharts(ctx, s, types.ChartFilter{
|
||||
NamespaceID: ns.ID,
|
||||
})
|
||||
req.NoError(err)
|
||||
ch1 := charts.FindByHandle("test_ns_1_chart_1")
|
||||
req.NotNil(ch1)
|
||||
|
||||
req.Equal(mod1.ID, ch1.Config.Reports[0].ModuleID)
|
||||
req.Equal(mod2.ID, ch1.Config.Reports[1].ModuleID)
|
||||
|
||||
// Pages
|
||||
pages, _, err := store.SearchComposePages(ctx, s, types.PageFilter{
|
||||
NamespaceID: ns.ID,
|
||||
})
|
||||
req.NoError(err)
|
||||
|
||||
pg1 := pages.FindByHandle("test_ns_1_page_1")
|
||||
req.NotNil(pg1)
|
||||
|
||||
req.Len(pg1.Blocks, 8)
|
||||
// @todo test page block references
|
||||
// spew.Dump(pg1.Blocks)
|
||||
|
||||
rpg1 := pages.FindByHandle("test_ns_1_record_page_1")
|
||||
req.NotNil(rpg1)
|
||||
req.Equal(mod1.ID, rpg1.ModuleID)
|
||||
|
||||
// Page layouts
|
||||
layouts, _, err := store.SearchComposePageLayouts(ctx, s, types.PageLayoutFilter{
|
||||
NamespaceID: ns.ID,
|
||||
})
|
||||
req.NoError(err)
|
||||
|
||||
ly1 := layouts.FindByHandle("test_ns_1_page_1_layout_1")
|
||||
req.NotNil(ly1)
|
||||
|
||||
ly2 := layouts.FindByHandle("test_ns_1_page_1_layout_2")
|
||||
req.NotNil(ly2)
|
||||
|
||||
// Record page layouts
|
||||
ly3 := layouts.FindByHandle("test_ns_1_record_page_1_layout_1")
|
||||
req.NotNil(ly3)
|
||||
|
||||
ly4 := layouts.FindByHandle("test_ns_1_record_page_1_layout_2")
|
||||
req.NotNil(ly4)
|
||||
})
|
||||
t.Run("corteza::system", func(t *testing.T) {
|
||||
// Users
|
||||
users, _, err := store.SearchUsers(ctx, s, systemTypes.UserFilter{})
|
||||
req.NoError(err)
|
||||
req.Len(users, 1)
|
||||
|
||||
u1 := users[0]
|
||||
req.Equal("test_user_1", u1.Handle)
|
||||
|
||||
// ApigwRoutes
|
||||
apigwroutes, _, err := store.SearchApigwRoutes(ctx, s, systemTypes.ApigwRouteFilter{})
|
||||
req.NoError(err)
|
||||
req.Len(apigwroutes, 1)
|
||||
|
||||
r1 := apigwroutes[0]
|
||||
req.Equal("/test/endpoint/1", r1.Endpoint)
|
||||
req.Equal(u1.ID, r1.CreatedBy)
|
||||
req.Equal(u1.ID, r1.UpdatedBy)
|
||||
|
||||
// ApigwFilters
|
||||
apigwfilter, _, err := store.SearchApigwFilters(ctx, s, systemTypes.ApigwFilterFilter{})
|
||||
req.NoError(err)
|
||||
req.Len(apigwfilter, 1)
|
||||
|
||||
f1 := apigwfilter[0]
|
||||
req.Equal(r1.ID, f1.Route)
|
||||
|
||||
// AuthClients
|
||||
clients, _, err := store.SearchAuthClients(ctx, s, systemTypes.AuthClientFilter{})
|
||||
req.NoError(err)
|
||||
req.Len(clients, 1)
|
||||
|
||||
c1 := clients[0]
|
||||
req.Equal("test_authclient_1", c1.Handle)
|
||||
req.Equal(u1.ID, c1.OwnedBy)
|
||||
req.Equal(u1.ID, c1.CreatedBy)
|
||||
req.Equal(u1.ID, c1.UpdatedBy)
|
||||
|
||||
// Queues
|
||||
queues, _, err := store.SearchQueues(ctx, s, systemTypes.QueueFilter{})
|
||||
req.NoError(err)
|
||||
req.Len(queues, 1)
|
||||
|
||||
q1 := queues[0]
|
||||
req.Equal("test_queue_1", q1.Queue)
|
||||
req.Equal(u1.ID, q1.CreatedBy)
|
||||
req.Equal(u1.ID, q1.UpdatedBy)
|
||||
|
||||
// Reports
|
||||
reports, _, err := store.SearchReports(ctx, s, systemTypes.ReportFilter{})
|
||||
req.NoError(err)
|
||||
req.Len(reports, 1)
|
||||
|
||||
rp1 := reports[0]
|
||||
req.Equal("test_report_1", rp1.Handle)
|
||||
req.Equal(u1.ID, rp1.OwnedBy)
|
||||
req.Equal(u1.ID, rp1.CreatedBy)
|
||||
req.Equal(u1.ID, rp1.UpdatedBy)
|
||||
|
||||
// Roles
|
||||
roles, _, err := store.SearchRoles(ctx, s, systemTypes.RoleFilter{})
|
||||
req.NoError(err)
|
||||
|
||||
rl1 := roles[0]
|
||||
req.Equal("test_role_1", rl1.Handle)
|
||||
|
||||
// Templates
|
||||
templates, _, err := store.SearchTemplates(ctx, s, systemTypes.TemplateFilter{})
|
||||
req.NoError(err)
|
||||
req.Len(templates, 1)
|
||||
|
||||
tpl1 := templates[0]
|
||||
req.Equal("test_template_1", tpl1.Handle)
|
||||
req.Equal(u1.ID, tpl1.OwnerID)
|
||||
|
||||
// DalConnections
|
||||
connections, _, err := store.SearchDalConnections(ctx, s, systemTypes.DalConnectionFilter{})
|
||||
req.NoError(err)
|
||||
req.Len(connections, 1)
|
||||
conn1 := connections[0]
|
||||
req.Equal("test_connection_1", conn1.Handle)
|
||||
|
||||
req.Equal(u1.ID, conn1.CreatedBy)
|
||||
req.Equal(u1.ID, conn1.UpdatedBy)
|
||||
|
||||
// DalSensitivityLevels
|
||||
dalsensitivitylevels, _, err := store.SearchDalSensitivityLevels(ctx, s, systemTypes.DalSensitivityLevelFilter{})
|
||||
req.NoError(err)
|
||||
req.Len(dalsensitivitylevels, 1)
|
||||
sens1 := dalsensitivitylevels[0]
|
||||
|
||||
req.Equal("test_sensitivity_level_1", sens1.Handle)
|
||||
req.Equal(u1.ID, sens1.CreatedBy)
|
||||
req.Equal(u1.ID, sens1.UpdatedBy)
|
||||
})
|
||||
t.Run("corteza::automation", func(t *testing.T) {
|
||||
// Workflows
|
||||
workflows, _, err := store.SearchAutomationWorkflows(ctx, s, automationTypes.WorkflowFilter{})
|
||||
req.NoError(err)
|
||||
req.Len(workflows, 1)
|
||||
|
||||
wf1 := workflows[0]
|
||||
req.Equal("test_workflow_1", wf1.Handle)
|
||||
|
||||
// Triggers
|
||||
triggers, _, err := store.SearchAutomationTriggers(ctx, s, automationTypes.TriggerFilter{})
|
||||
req.NoError(err)
|
||||
req.Len(triggers, 1)
|
||||
|
||||
t1 := triggers[0]
|
||||
req.Equal(wf1.ID, t1.WorkflowID)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func assignModuleFields(modules types.ModuleSet, fields types.ModuleFieldSet) {
|
||||
for _, m := range modules {
|
||||
for _, f := range fields {
|
||||
if m.ID == f.ModuleID {
|
||||
m.Fields = append(m.Fields, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user