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:
42
server/tests/helpers/app.go
Normal file
42
server/tests/helpers/app.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/app"
|
||||
"github.com/cortezaproject/corteza/server/pkg/cli"
|
||||
"github.com/cortezaproject/corteza/server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza/server/pkg/options"
|
||||
"github.com/cortezaproject/corteza/server/pkg/rand"
|
||||
"github.com/cortezaproject/corteza/server/system/types"
|
||||
|
||||
// Explicitly register SQLite (not done in the app as for testing only)
|
||||
_ "github.com/cortezaproject/corteza/server/store/adapters/rdbms/drivers/sqlite"
|
||||
)
|
||||
|
||||
func NewIntegrationTestApp(ctx context.Context, initTestServices func(*app.CortezaApp) error) *app.CortezaApp {
|
||||
// Enforce debug logger for tests
|
||||
logger.SetDefault(logger.MakeDebugLogger())
|
||||
|
||||
var (
|
||||
a = app.New()
|
||||
)
|
||||
|
||||
a.Opt = options.Init()
|
||||
|
||||
// When running integration tests, we want to upgrade the db. Always.
|
||||
a.Opt.Upgrade.Always = true
|
||||
|
||||
// Create a new JWT secret (to prevent any security weirdness)
|
||||
a.Opt.Auth.Secret = string(rand.Bytes(32))
|
||||
a.Opt.Auth.DefaultClient = ""
|
||||
|
||||
a.Log = logger.Default()
|
||||
|
||||
a.DefaultAuthClient = &types.AuthClient{ID: 1, Handle: "test-auth-client", Secret: "integration-tests"}
|
||||
|
||||
cli.HandleError(a.InitStore(ctx))
|
||||
cli.HandleError(initTestServices(a))
|
||||
cli.HandleError(a.InitServices(ctx))
|
||||
return a
|
||||
}
|
||||
172
server/tests/helpers/assert.go
Normal file
172
server/tests/helpers/assert.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
)
|
||||
|
||||
type (
|
||||
assertFn func(*http.Response, *http.Request) error
|
||||
|
||||
StdErrorResponse struct {
|
||||
Error struct {
|
||||
Message string
|
||||
}
|
||||
}
|
||||
|
||||
RecordValueErrorSetResponse struct {
|
||||
Error struct {
|
||||
Message string
|
||||
Details []types.RecordValueError
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// decodes response body to given struct
|
||||
func DecodeBody(rsp *http.Response, s interface{}) error {
|
||||
if err := json.NewDecoder(rsp.Body).Decode(&s); err != nil {
|
||||
return fmt.Errorf("could not decode body: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns first input that could be an error
|
||||
func firstErr(ee ...interface{}) error {
|
||||
for _, e := range ee {
|
||||
switch t := e.(type) {
|
||||
case error:
|
||||
if t != nil {
|
||||
return t
|
||||
}
|
||||
case StdErrorResponse:
|
||||
if t.Error.Message != "" {
|
||||
return errors.New(t.Error.Message)
|
||||
}
|
||||
case string:
|
||||
if t != "" {
|
||||
return errors.New(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertNoErrors ensures there are no errors in the response
|
||||
func AssertNoErrors(rsp *http.Response, _ *http.Request) (err error) {
|
||||
tmp := StdErrorResponse{}
|
||||
return firstErr(DecodeBody(rsp, &tmp), tmp)
|
||||
}
|
||||
|
||||
// Asserts that all expected errors are returned
|
||||
//
|
||||
// # Compares each error by Kind, Message and Meta.field
|
||||
//
|
||||
// Note: This assertion always expects errors!
|
||||
func AssertRecordValueError(exp ...*types.RecordValueError) assertFn {
|
||||
return func(rsp *http.Response, _ *http.Request) (err error) {
|
||||
rcvd := RecordValueErrorSetResponse{}
|
||||
if err = DecodeBody(rsp, &rcvd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(rcvd.Error.Details) == 0 {
|
||||
return fmt.Errorf("expecting value errors, none received")
|
||||
}
|
||||
|
||||
expLoop:
|
||||
for _, e := range exp {
|
||||
for _, r := range rcvd.Error.Details {
|
||||
if e.Kind != r.Kind {
|
||||
continue
|
||||
}
|
||||
if e.Message != r.Message {
|
||||
continue
|
||||
}
|
||||
if e.Meta["field"] != r.Meta["field"] {
|
||||
continue
|
||||
}
|
||||
|
||||
// found expected error
|
||||
continue expLoop
|
||||
}
|
||||
|
||||
// did not find expected error
|
||||
return fmt.Errorf("did not find expected error %v", e)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Dump can be put into Assert()
|
||||
func Dump(rsp *http.Response, _ *http.Request) (err error) {
|
||||
var payload interface{}
|
||||
if err = DecodeBody(rsp, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertError ensures there are no errors in the response
|
||||
func AssertError(expectedError string) assertFn {
|
||||
return func(rsp *http.Response, _ *http.Request) (err error) {
|
||||
tmp := StdErrorResponse{}
|
||||
if err = DecodeBody(rsp, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tmp.Error.Message == "" {
|
||||
return fmt.Errorf("No error, expecting: %v", expectedError)
|
||||
}
|
||||
|
||||
if expectedError != tmp.Error.Message {
|
||||
return fmt.Errorf("Expecting error %v, got: %v", expectedError, tmp.Error.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AssertBody compares the raw body to the provided string
|
||||
func AssertBody(expected string) assertFn {
|
||||
return func(rsp *http.Response, _ *http.Request) (err error) {
|
||||
bb, err := ioutil.ReadAll(rsp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
got := strings.Trim(string(bb), " \n")
|
||||
if expected != got {
|
||||
return fmt.Errorf("Expecting: %v, got: %v", expected, got)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AssertErrorP checks if the expected error is part of the error message
|
||||
func AssertErrorP(expectedError string) assertFn {
|
||||
return func(rsp *http.Response, _ *http.Request) (err error) {
|
||||
tmp := StdErrorResponse{}
|
||||
if err = DecodeBody(rsp, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tmp.Error.Message == "" {
|
||||
return fmt.Errorf("No error, expecting error with: %v", expectedError)
|
||||
}
|
||||
|
||||
if !strings.Contains(tmp.Error.Message, expectedError) {
|
||||
return fmt.Errorf("Expecting error with %v, got: %v", expectedError, tmp.Error.Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
23
server/tests/helpers/auth.go
Normal file
23
server/tests/helpers/auth.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/steinfletcher/apitest"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
)
|
||||
|
||||
func BindAuthMiddleware(r chi.Router) {
|
||||
r.Use(
|
||||
auth.HttpTokenVerifier,
|
||||
auth.HttpTokenValidator(),
|
||||
)
|
||||
}
|
||||
|
||||
func ReqHeaderRawAuthBearer(token []byte) apitest.Intercept {
|
||||
return func(req *http.Request) {
|
||||
req.Header.Set("Authorization", "Bearer "+string(token))
|
||||
}
|
||||
}
|
||||
16
server/tests/helpers/env.go
Normal file
16
server/tests/helpers/env.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/joho/godotenv"
|
||||
"os"
|
||||
)
|
||||
|
||||
// RecursiveDotEnvLoad loads ENV variables from .evn files 3 levels down
|
||||
func RecursiveDotEnvLoad() {
|
||||
for _, loc := range []string{".env", "../.env", "../../.env"} {
|
||||
if _, err := os.Stat(loc); err == nil {
|
||||
godotenv.Load(loc)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
11
server/tests/helpers/json.go
Normal file
11
server/tests/helpers/json.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package helpers
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
func JSON(i interface{}) string {
|
||||
if b, err := json.Marshal(i); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
77
server/tests/helpers/labels.go
Normal file
77
server/tests/helpers/labels.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/label/types"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
"github.com/steinfletcher/apitest"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func SetLabelsViaAPI(api *apitest.APITest, t *testing.T, endpoint string, in, out interface{}) {
|
||||
var (
|
||||
payload = struct {
|
||||
Response interface{}
|
||||
}{}
|
||||
req *apitest.Request
|
||||
)
|
||||
|
||||
payload.Response = out
|
||||
|
||||
if strings.HasPrefix(endpoint, "PUT ") {
|
||||
// a little workaround for our inconsistencies...
|
||||
req = api.Put(endpoint[4:])
|
||||
} else {
|
||||
req = api.Post(endpoint)
|
||||
}
|
||||
|
||||
req.JSON(JSON(in)).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(AssertNoErrors).
|
||||
End().
|
||||
JSON(&payload)
|
||||
}
|
||||
|
||||
func SearchWithLabelsViaAPI(api *apitest.APITest, t *testing.T, endpoint string, res interface{}, labels url.Values) {
|
||||
var payload = struct{ Response struct{ Set interface{} } }{}
|
||||
|
||||
payload.Response.Set = res
|
||||
|
||||
api.
|
||||
Get(endpoint).
|
||||
QueryCollection(labels).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(AssertNoErrors).
|
||||
End().
|
||||
JSON(&payload)
|
||||
}
|
||||
|
||||
func LoadLabelsFromStore(t *testing.T, s store.Storer, resKind string, id uint64) map[string]types.LabelValue {
|
||||
ll, _, err := s.SearchLabels(context.Background(), types.LabelFilter{Kind: resKind})
|
||||
require.NoError(t, err)
|
||||
if len(ll) == 0 {
|
||||
// small adjustment
|
||||
return nil
|
||||
}
|
||||
|
||||
return ll.FilterByResource(resKind, id)
|
||||
}
|
||||
|
||||
func LabelsToStrings(labels map[string]types.LabelValue) map[string]string {
|
||||
if labels == nil {
|
||||
return nil
|
||||
}
|
||||
result := make(map[string]string, len(labels))
|
||||
for k, v := range labels {
|
||||
result[k] = v.Val
|
||||
}
|
||||
return result
|
||||
}
|
||||
120
server/tests/helpers/rbac.go
Normal file
120
server/tests/helpers/rbac.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
automationTypes "github.com/cortezaproject/corteza/server/automation/types"
|
||||
composeTypes "github.com/cortezaproject/corteza/server/compose/types"
|
||||
"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/rbac"
|
||||
"github.com/cortezaproject/corteza/server/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
myRoleGetter interface {
|
||||
MyRole() uint64
|
||||
}
|
||||
)
|
||||
|
||||
func UpdateRBAC(rr ...uint64) {
|
||||
// make sure event listener for role changes is removed
|
||||
eventbus.Service().UnregisterByResource("system:role")
|
||||
|
||||
// convert given list of role IDs to RBAC roles
|
||||
// and update service
|
||||
ccr := []*rbac.Role{}
|
||||
|
||||
// Make sure all bypass roles are set on RBAC service
|
||||
for _, r := range auth.ServiceUser().Roles() {
|
||||
ccr = append(ccr, rbac.BypassRole.Make(r, auth.BypassRoleHandle))
|
||||
}
|
||||
|
||||
for _, r := range rr {
|
||||
ccr = append(ccr, rbac.CommonRole.Make(r, "integration-tests"))
|
||||
}
|
||||
rbac.Global().Clear()
|
||||
rbac.Global().UpdateRoles(ccr...)
|
||||
}
|
||||
|
||||
func AllowMe(mrg myRoleGetter, r string, oo ...string) {
|
||||
for _, o := range oo {
|
||||
Grant(rbac.AllowRule(mrg.MyRole(), r, o))
|
||||
}
|
||||
}
|
||||
|
||||
func Allow(role *types.Role, r string, oo ...string) {
|
||||
for _, o := range oo {
|
||||
Grant(rbac.AllowRule(role.ID, r, o))
|
||||
}
|
||||
}
|
||||
|
||||
func DenyMe(mrg myRoleGetter, r string, oo ...string) {
|
||||
for _, o := range oo {
|
||||
Grant(rbac.DenyRule(mrg.MyRole(), r, o))
|
||||
}
|
||||
}
|
||||
|
||||
func Deny(role *types.Role, r string, oo ...string) {
|
||||
for _, o := range oo {
|
||||
Grant(rbac.DenyRule(role.ID, r, o))
|
||||
}
|
||||
}
|
||||
|
||||
func Grant(rr ...*rbac.Rule) {
|
||||
cli.HandleError(rbac.Global().Grant(context.Background(), rr...))
|
||||
}
|
||||
|
||||
// Common RBAC presets
|
||||
|
||||
func AllowMeModuleCreate(mrg myRoleGetter) {
|
||||
AllowMe(mrg, composeTypes.NamespaceRbacResource(0), "module.create")
|
||||
}
|
||||
|
||||
func AllowMeWorkflowSearch(mrg myRoleGetter) {
|
||||
AllowMe(mrg, automationTypes.WorkflowRbacResource(0), "workflows.search")
|
||||
AllowMe(mrg, automationTypes.WorkflowRbacResource(0), "triggers.search")
|
||||
}
|
||||
|
||||
func AllowMeModuleSearch(mrg myRoleGetter) {
|
||||
AllowMe(mrg, composeTypes.NamespaceRbacResource(0), "modules.search")
|
||||
AllowMe(mrg, composeTypes.ModuleRbacResource(0, 0), "read")
|
||||
}
|
||||
|
||||
func AllowMeModuleCRUD(mrg myRoleGetter) {
|
||||
AllowMe(mrg, composeTypes.NamespaceRbacResource(0), "module.create")
|
||||
AllowMe(mrg, composeTypes.NamespaceRbacResource(0), "modules.search")
|
||||
AllowMe(mrg, composeTypes.ModuleRbacResource(0, 0), "read")
|
||||
AllowMe(mrg, composeTypes.ModuleRbacResource(0, 0), "update")
|
||||
AllowMe(mrg, composeTypes.ModuleRbacResource(0, 0), "delete")
|
||||
}
|
||||
|
||||
func AllowMeRecordCRUD(mrg myRoleGetter) {
|
||||
AllowMe(mrg, composeTypes.ModuleRbacResource(0, 0), "record.create")
|
||||
AllowMe(mrg, composeTypes.ModuleRbacResource(0, 0), "records.search")
|
||||
AllowMe(mrg, composeTypes.RecordRbacResource(0, 0, 0), "read")
|
||||
AllowMe(mrg, composeTypes.RecordRbacResource(0, 0, 0), "update")
|
||||
AllowMe(mrg, composeTypes.RecordRbacResource(0, 0, 0), "delete")
|
||||
AllowMe(mrg, composeTypes.ModuleFieldRbacResource(0, 0, 0), "record.value.update")
|
||||
AllowMe(mrg, composeTypes.ModuleFieldRbacResource(0, 0, 0), "record.value.read")
|
||||
}
|
||||
|
||||
func AllowMeDalConnectionSearch(mrg myRoleGetter) {
|
||||
AllowMe(mrg, types.ComponentRbacResource(), "dal-connections.search")
|
||||
AllowMe(mrg, types.DalConnectionRbacResource(0), "read")
|
||||
}
|
||||
|
||||
func AllowMeDalConnectionCRUD(mrg myRoleGetter) {
|
||||
AllowMe(mrg, types.ComponentRbacResource(),
|
||||
"dal-connection.create",
|
||||
"dal-connections.search",
|
||||
)
|
||||
|
||||
AllowMe(mrg, types.DalConnectionRbacResource(0),
|
||||
"read",
|
||||
"update",
|
||||
"delete",
|
||||
"dal-config.manage",
|
||||
)
|
||||
}
|
||||
40
server/tests/helpers/upload.go
Normal file
40
server/tests/helpers/upload.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"net/textproto"
|
||||
"testing"
|
||||
|
||||
"github.com/steinfletcher/apitest"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func InitFileUpload(t *testing.T, apiTest *apitest.APITest, endpoint string, form map[string]string, file []byte, name, mimetype string) *apitest.Response {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
for k, v := range form {
|
||||
require.NoError(t, writer.WriteField(k, v))
|
||||
}
|
||||
|
||||
header := make(textproto.MIMEHeader)
|
||||
header.Set("Content-Disposition",
|
||||
fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
|
||||
"upload", name))
|
||||
header.Set("Content-Type", mimetype)
|
||||
part, err := writer.CreatePart(header)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = part.Write(file)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, writer.Close())
|
||||
|
||||
return apiTest.
|
||||
Post(endpoint).
|
||||
Body(body.String()).
|
||||
Header("Content-Type", writer.FormDataContentType()).
|
||||
Header("Accept", "application/json").
|
||||
Expect(t)
|
||||
}
|
||||
Reference in New Issue
Block a user