From ae3d74992e6034fafa80389d523debd6520d1238 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Mon, 9 Sep 2019 03:18:07 +0200 Subject: [PATCH] Added integration tests for channels --- tests/helpers/assert.go | 26 +++++- tests/messaging/channel_attach_test.go | 83 ++++++++++++++++++ tests/messaging/channel_create_test.go | 42 ++++++++++ tests/messaging/channel_flags_test.go | 64 ++++++++++++++ tests/messaging/channel_list_test.go | 19 +++++ tests/messaging/channel_members_test.go | 76 +++++++++++++++++ tests/messaging/channel_state_test.go | 37 ++++++++ tests/messaging/channel_test.go | 77 +++++++++++++++++ tests/messaging/channel_update_test.go | 84 +++++++++++++++++++ tests/messaging/channels_test.go | 39 --------- tests/messaging/main_test.go | 107 +++++++++++++++++++++--- 11 files changed, 599 insertions(+), 55 deletions(-) create mode 100644 tests/messaging/channel_attach_test.go create mode 100644 tests/messaging/channel_create_test.go create mode 100644 tests/messaging/channel_flags_test.go create mode 100644 tests/messaging/channel_list_test.go create mode 100644 tests/messaging/channel_members_test.go create mode 100644 tests/messaging/channel_state_test.go create mode 100644 tests/messaging/channel_test.go create mode 100644 tests/messaging/channel_update_test.go delete mode 100644 tests/messaging/channels_test.go diff --git a/tests/helpers/assert.go b/tests/helpers/assert.go index b248e143f..fb8cf6c07 100644 --- a/tests/helpers/assert.go +++ b/tests/helpers/assert.go @@ -8,7 +8,7 @@ import ( ) type ( - Assert func(*http.Response, *http.Request) error + assertFn func(*http.Response, *http.Request) error StdErrorResponse struct{ Error struct{ Message string } } ) @@ -44,8 +44,28 @@ func firstErr(ee ...interface{}) error { return nil } -// Ensures there are no errors in the response -func NoErrors(rsp *http.Response, _ *http.Request) (err error) { +// 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) } + +// 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 errors.Errorf("Could not decode body: %v", err) + } + + if tmp.Error.Message == "" { + return errors.Errorf("No error, expecting: %v", expectedError) + } + + if expectedError != tmp.Error.Message { + return errors.Errorf("Expecting error %v, got: %v", expectedError, tmp.Error.Message) + } + + return nil + } +} diff --git a/tests/messaging/channel_attach_test.go b/tests/messaging/channel_attach_test.go new file mode 100644 index 000000000..f1e5f32cc --- /dev/null +++ b/tests/messaging/channel_attach_test.go @@ -0,0 +1,83 @@ +package messaging + +import ( + "bytes" + "fmt" + "mime/multipart" + "net/http" + "net/url" + "testing" + "time" + + "github.com/steinfletcher/apitest" + jsonpath "github.com/steinfletcher/apitest-jsonpath" + "github.com/stretchr/testify/assert" + + "github.com/cortezaproject/corteza-server/messaging/types" + "github.com/cortezaproject/corteza-server/tests/helpers" +) + +func chAttach(h helper, ch *types.Channel, file []byte) *apitest.Response { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, err := writer.CreateFormFile("upload", "test.txt") + h.a.NoError(err) + + _, err = part.Write(file) + h.a.NoError(err) + h.a.NoError(writer.Close()) + + return h.testAPI(). + Debug(). + Post(fmt.Sprintf("/channels/%v/attach", ch.ID)). + Body(body.String()). + ContentType(writer.FormDataContentType()). + Expect(h.t). + Status(http.StatusOK) +} + +// Non members should not be able to attach files to non-public channels +func TestChannelAttachNotMember(t *testing.T) { + h := newHelper(t) + + ch := h.makePrivateCh() + + chAttach(h, ch, []byte("NOPE")). + Assert(helpers.AssertError("messaging.service.NoPermissions")). + End() +} + +func TestChannelAttach(t *testing.T) { + h := newHelper(t) + + uploadFileContent := "hello corteza, time here is " + time.Now().String() + + ch := h.makePublicCh() + h.makeMember(ch, h.cUser) + + out := chAttach(h, ch, []byte(uploadFileContent)). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Present(`$.response.url`)). + Assert(jsonpath.Present(`$.response.previewUrl`)). + End() + + // Now, fetch uploaded file and check the content + var rval = struct { + Response struct { + Url string + PreviewUrl string + } + }{} + out.JSON(&rval) + attUrl, err := url.Parse(rval.Response.Url) + assert.NoError(t, err) + + h.testAPI(). + Get(attUrl.Path). + Query("sign", attUrl.Query().Get("sign")). + Query("userID", attUrl.Query().Get("userID")). + Expect(t). + Status(http.StatusOK). + Body(uploadFileContent). + End() +} diff --git a/tests/messaging/channel_create_test.go b/tests/messaging/channel_create_test.go new file mode 100644 index 000000000..ba2343ebb --- /dev/null +++ b/tests/messaging/channel_create_test.go @@ -0,0 +1,42 @@ +package messaging + +import ( + "net/http" + "strconv" + "testing" + + jsonpath "github.com/steinfletcher/apitest-jsonpath" + + "github.com/cortezaproject/corteza-server/messaging/types" + "github.com/cortezaproject/corteza-server/tests/helpers" +) + +func TestChannelCreateDenied(t *testing.T) { + h := newHelper(t) + h.deny(types.MessagingPermissionResource, "channel.public.create") + + h.testAPI(). + Post("/channels/"). + JSON(`{"name":"test channel","type":"public"}`). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("messaging.service.NoPermissions")). + End() +} + +func TestChannelCreate(t *testing.T) { + h := newHelper(t) + h.allow(types.MessagingPermissionResource, "channel.public.create") + + h.testAPI(). + Post("/channels/"). + JSON(`{"name":"test channel","type":"public"}`). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Present(`$.response.name`)). + Assert(jsonpath.Present(`$.response.channelID`)). + // Creator should be a member + Assert(jsonpath.Contains(`$.response.members`, strconv.FormatUint(h.cUser.ID, 10))). + End() +} diff --git a/tests/messaging/channel_flags_test.go b/tests/messaging/channel_flags_test.go new file mode 100644 index 000000000..096db1113 --- /dev/null +++ b/tests/messaging/channel_flags_test.go @@ -0,0 +1,64 @@ +package messaging + +import ( + "fmt" + "net/http" + "testing" + + "github.com/steinfletcher/apitest" + + "github.com/cortezaproject/corteza-server/messaging/types" + "github.com/cortezaproject/corteza-server/tests/helpers" +) + +func TestChannelSetFlag(t *testing.T) { + h := newHelper(t) + + ch := h.makePublicCh() + h.makeMember(ch, h.cUser) + + flagCheck := func(ID uint64, flag string) { + mm, err := h.repoChMember().Find(&types.ChannelMemberFilter{ChannelID: ID, MemberID: h.cUser.ID}) + h.a.NoError(err) + h.a.Len(mm, 1, "expecting 1 member") + h.a.Equal(flag, string(mm[0].Flag), "expecting flags to match") + } + + flagChannel := func(ID uint64, flag string) *apitest.Response { + return h.testAPI(). + Put(fmt.Sprintf("/channels/%d/flag", ID)). + FormData("flag", flag). + Expect(t). + Status(http.StatusOK) + } + + flagChannelOK := func(ID uint64, flag string) { + flagChannel(ID, flag). + Assert(helpers.AssertNoErrors). + End() + + flagCheck(ID, flag) + } + + unflagChannel := func(ID uint64) { + h.testAPI(). + Delete(fmt.Sprintf("/channels/%d/flag", ID)). + Expect(t). + Status(http.StatusOK). + End() + + flagCheck(ID, string(types.ChannelMembershipFlagNone)) + } + + flagCheck(ch.ID, string(types.ChannelMembershipFlagNone)) + + flagChannelOK(ch.ID, string(types.ChannelMembershipFlagPinned)) + flagChannelOK(ch.ID, string(types.ChannelMembershipFlagHidden)) + flagChannelOK(ch.ID, string(types.ChannelMembershipFlagIgnored)) + + flagChannel(ch.ID, "foo"). + Assert(helpers.AssertError("invalid flag")) + flagCheck(ch.ID, string(types.ChannelMembershipFlagIgnored)) + + unflagChannel(ch.ID) +} diff --git a/tests/messaging/channel_list_test.go b/tests/messaging/channel_list_test.go new file mode 100644 index 000000000..de100cd54 --- /dev/null +++ b/tests/messaging/channel_list_test.go @@ -0,0 +1,19 @@ +package messaging + +import ( + "net/http" + "testing" + + "github.com/cortezaproject/corteza-server/tests/helpers" +) + +func TestChannelList(t *testing.T) { + h := newHelper(t) + + h.testAPI(). + Get("/channels/"). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() +} diff --git a/tests/messaging/channel_members_test.go b/tests/messaging/channel_members_test.go new file mode 100644 index 000000000..fcbd5021e --- /dev/null +++ b/tests/messaging/channel_members_test.go @@ -0,0 +1,76 @@ +package messaging + +import ( + "fmt" + "net/http" + "testing" + + "github.com/titpetric/factory" + + "github.com/cortezaproject/corteza-server/messaging/types" + sysType "github.com/cortezaproject/corteza-server/system/types" + "github.com/cortezaproject/corteza-server/tests/helpers" +) + +func TestChannelMemberList(t *testing.T) { + h := newHelper(t) + + ch := h.makePublicCh() + + h.testAPI(). + Get(fmt.Sprintf("/channels/%d/members", ch.ID)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() +} + +func TestChannelMemberJoinSelf(t *testing.T) { + h := newHelper(t) + + ch := h.makePublicCh() + + h.testAPI(). + Put(fmt.Sprintf("/channels/%d/members/%d", ch.ID, h.cUser.ID)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() + + h.chAssertMember(ch, h.cUser, types.ChannelMembershipTypeMember) +} + +func TestChannelMemberLeaveSelf(t *testing.T) { + h := newHelper(t) + + ch := h.makePublicCh() + h.makeMember(ch, h.cUser) + + h.testAPI(). + Delete(fmt.Sprintf("/channels/%d/members/%d", ch.ID, h.cUser.ID)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() + + h.chAssertNotMember(ch, h.cUser) +} + +func TestChannelMemberInvite(t *testing.T) { + t.SkipNow() + + h := newHelper(t) + ch := h.makePublicCh() + h.allow(ch.PermissionResource(), "members.manage") + + invitee := &sysType.User{ID: factory.Sonyflake.NextID()} + + h.testAPI(). + Put(fmt.Sprintf("/channels/%d/members/%d", ch.ID, invitee.ID)). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() + + h.chAssertMember(ch, invitee, types.ChannelMembershipTypeInvitee) +} diff --git a/tests/messaging/channel_state_test.go b/tests/messaging/channel_state_test.go new file mode 100644 index 000000000..04e58c8b8 --- /dev/null +++ b/tests/messaging/channel_state_test.go @@ -0,0 +1,37 @@ +package messaging + +import ( + "fmt" + "net/http" + "testing" + + "github.com/cortezaproject/corteza-server/internal/permissions" + "github.com/cortezaproject/corteza-server/messaging/types" + "github.com/cortezaproject/corteza-server/tests/helpers" +) + +func TestChannelAlterState(t *testing.T) { + h := newHelper(t) + ch := h.makePublicCh() + + stateUrl := fmt.Sprintf("/channels/%d/state", ch.ID) + + testState := func(state string) { + p.ClearGrants() + h.mockPermissionsWithAccess() + h.allow(types.ChannelPermissionResource.AppendWildcard(), permissions.Operation(state)) + + h.testAPI(). + Put(stateUrl). + FormData("state", state). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertNoErrors). + End() + } + + testState("archive") + testState("unarchive") + testState("delete") + testState("undelete") +} diff --git a/tests/messaging/channel_test.go b/tests/messaging/channel_test.go new file mode 100644 index 000000000..c471ee0ae --- /dev/null +++ b/tests/messaging/channel_test.go @@ -0,0 +1,77 @@ +package messaging + +import ( + "context" + "time" + + "github.com/titpetric/factory" + + "github.com/cortezaproject/corteza-server/messaging/repository" + "github.com/cortezaproject/corteza-server/messaging/types" + sysTypes "github.com/cortezaproject/corteza-server/system/types" +) + +func (h helper) repoChannel() repository.ChannelRepository { + var ( + ctx = context.Background() + db = factory.Database.MustGet("messaging").With(ctx) + ) + + return repository.Channel(ctx, db) +} + +func (h helper) repoChMember() repository.ChannelMemberRepository { + var ( + ctx = context.Background() + db = factory.Database.MustGet("messaging").With(ctx) + ) + + return repository.ChannelMember(ctx, db) +} + +func (h helper) makePublicCh() *types.Channel { + ch, err := h.repoChannel().Create(&types.Channel{ + Name: "Test channel " + time.Now().String(), + Type: types.ChannelTypePublic, + }) + + h.a.NoError(err) + return ch +} + +func (h helper) makePrivateCh() *types.Channel { + ch, err := h.repoChannel().Create(&types.Channel{ + Name: "Test channel " + time.Now().String(), + Type: types.ChannelTypePrivate, + }) + + h.a.NoError(err) + return ch +} + +func (h helper) makeMember(ch *types.Channel, u *sysTypes.User) *types.ChannelMember { + m, err := h. + repoChMember(). + Create(&types.ChannelMember{ChannelID: ch.ID, UserID: h.cUser.ID, Type: types.ChannelMembershipTypeMember}) + h.a.NoError(err) + + return m +} + +func (h helper) chAssertNotMember(ch *types.Channel, u *sysTypes.User) { + mm, err := h.repoChMember().Find(&types.ChannelMemberFilter{ChannelID: ch.ID, MemberID: h.cUser.ID}) + h.a.NoError(err) + h.a.NotNil(mm) + h.a.NotContains(mm.AllMemberIDs(), u.ID, "not expecting to find a member") + +} + +func (h helper) chAssertMember(ch *types.Channel, u *sysTypes.User, typ types.ChannelMembershipType) { + mm, err := h.repoChMember().Find(&types.ChannelMemberFilter{ChannelID: ch.ID, MemberID: h.cUser.ID}) + + h.a.NoError(err) + h.a.NotNil(mm) + h.a.NotNil(mm.FindByUserID(u.ID), "expecting to find a member") + h.a.Equal(typ, mm.FindByUserID(u.ID).Type, "expecting to find a member") + +} diff --git a/tests/messaging/channel_update_test.go b/tests/messaging/channel_update_test.go new file mode 100644 index 000000000..957eb1462 --- /dev/null +++ b/tests/messaging/channel_update_test.go @@ -0,0 +1,84 @@ +package messaging + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "testing" + + "github.com/steinfletcher/apitest" + jsonpath "github.com/steinfletcher/apitest-jsonpath" + "github.com/titpetric/factory" + + "github.com/cortezaproject/corteza-server/messaging/rest/request" + "github.com/cortezaproject/corteza-server/messaging/types" + "github.com/cortezaproject/corteza-server/tests/helpers" +) + +func (h helper) chUpdate(ch *request.ChannelUpdate) *apitest.Response { + payload, err := json.Marshal(ch) + h.a.NoError(err) + + return h.testAPI(). + Put(fmt.Sprintf("/channels/%v", ch.ChannelID)). + JSON(string(payload)). + Expect(h.t). + Status(http.StatusOK) +} + +func channelToRequest(ch *types.Channel) *request.ChannelUpdate { + req := &request.ChannelUpdate{ + ChannelID: ch.ID, + Name: ch.Name, + Topic: ch.Topic, + MembershipPolicy: ch.MembershipPolicy, + Type: ch.Type.String(), + } + + return req +} + +func TestChannelUpdateNonexistent(t *testing.T) { + h := newHelper(t) + + req := &request.ChannelUpdate{ + ChannelID: factory.Sonyflake.NextID(), + Name: "some name", + } + + h.chUpdate(req). + Assert(helpers.AssertError("messaging.repository.ChannelNotFound")). + End() + +} + +func TestChannelUpdateDenied(t *testing.T) { + h := newHelper(t) + ch := h.makePublicCh() + + h.deny(ch.PermissionResource(), "update") + + req := channelToRequest(ch) + req.Name = "Updated name" + + h.chUpdate(req). + Assert(helpers.AssertError("messaging.service.NoPermissions")). + End() +} + +func TestChannelUpdate(t *testing.T) { + h := newHelper(t) + ch := h.makePublicCh() + + h.allow(ch.PermissionResource(), "update") + + req := channelToRequest(ch) + req.Name = "Updated name" + + h.chUpdate(req). + Assert(helpers.AssertNoErrors). + Assert(jsonpath.Equal(`$.response.name`, req.Name)). + Assert(jsonpath.Equal(`$.response.channelID`, strconv.FormatUint(ch.ID, 10))). + End() +} diff --git a/tests/messaging/channels_test.go b/tests/messaging/channels_test.go deleted file mode 100644 index 4a9c7748f..000000000 --- a/tests/messaging/channels_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package messaging - -import ( - "net/http" - "testing" - - "github.com/cortezaproject/corteza-server/system/types" - "github.com/cortezaproject/corteza-server/tests/helpers" -) - -func TestChannelList(t *testing.T) { - - NewApiTest("get list of channels", &types.User{ID: 5}). - Get("/channels/"). - Expect(t). - Status(http.StatusOK). - Assert(helpers.NoErrors). - End() -} - -func TestChannelRead(t *testing.T) { - NewApiTest("find single channel by ID", &types.User{ID: 5}). - Get("/channels/324234"). - Expect(t). - Status(http.StatusOK). - Assert(helpers.NoErrors). - End() -} - -func TestChannelCreate(t *testing.T) { - t.Skip() - NewApiTest("create channel", &types.User{ID: 5}). - Post("/channels/"). - Body(`{"name":"test channel"}`). - Expect(t). - Status(http.StatusOK). - Assert(helpers.NoErrors). - End() -} diff --git a/tests/messaging/main_test.go b/tests/messaging/main_test.go index d23f02e20..c94d3e6e5 100644 --- a/tests/messaging/main_test.go +++ b/tests/messaging/main_test.go @@ -6,27 +6,48 @@ import ( "testing" _ "github.com/joho/godotenv/autoload" + "github.com/spf13/afero" + "github.com/steinfletcher/apitest" + "github.com/stretchr/testify/require" + "github.com/titpetric/factory" "github.com/go-chi/chi" "go.uber.org/zap" - "github.com/steinfletcher/apitest" - + "github.com/cortezaproject/corteza-server/internal/auth" + "github.com/cortezaproject/corteza-server/internal/permissions" + "github.com/cortezaproject/corteza-server/internal/rand" + "github.com/cortezaproject/corteza-server/internal/store" "github.com/cortezaproject/corteza-server/messaging" "github.com/cortezaproject/corteza-server/messaging/rest" + "github.com/cortezaproject/corteza-server/messaging/service" + "github.com/cortezaproject/corteza-server/messaging/types" "github.com/cortezaproject/corteza-server/pkg/api" "github.com/cortezaproject/corteza-server/pkg/cli" "github.com/cortezaproject/corteza-server/pkg/logger" - "github.com/cortezaproject/corteza-server/system/types" + sysTypes "github.com/cortezaproject/corteza-server/system/types" "github.com/cortezaproject/corteza-server/tests/helpers" ) +type ( + helper struct { + t *testing.T + a *require.Assertions + + cUser *sysTypes.User + roleID uint64 + } +) + var ( cfg *cli.Config r chi.Router + p = permissions.NewTestService() ) func InitConfig() { + var err error + if cfg != nil { return } @@ -41,11 +62,18 @@ func InitConfig() { cfg.Init() - if err := cfg.RootCommandDBSetup.Run(ctx, nil, cfg); err != nil { + auth.SetupDefault(string(rand.Bytes(32)), 10) + + if err = cfg.RootCommandDBSetup.Run(ctx, nil, cfg); err != nil { panic(err) } logger.SetDefault(log) + service.DefaultPermissions = p + if service.DefaultStore, err = store.NewWithAfero(afero.NewMemMapFs(), "test"); err != nil { + panic(err) + } + cfg.InitServices(ctx, cfg) } @@ -63,16 +91,69 @@ func InitApp() { rest.MountRoutes(r) } -func NewApiTest(name string, user *types.User) *apitest.APITest { - InitApp() - - return apitest. - New(name). - Handler(r). - Intercept(helpers.ReqHeaderAuthBearer(user)) -} - func TestMain(m *testing.M) { InitApp() os.Exit(m.Run()) } + +func newHelper(t *testing.T) helper { + h := helper{ + t: t, + a: require.New(t), + roleID: factory.Sonyflake.NextID(), + cUser: &sysTypes.User{ + ID: factory.Sonyflake.NextID(), + }, + } + + h.cUser.SetRoles([]uint64{h.roleID}) + + p.ClearGrants() + + // Setup permissions with allowed access to messaging + h.mockPermissions( + permissions.AllowRule(permissions.EveryoneRoleID, types.MessagingPermissionResource, "access"), + ) + + return h +} + +// apitest basics, initialize, set handler, add auth +func (h helper) testAPI() *apitest.APITest { + InitApp() + + return apitest. + New(). + Handler(r). + Intercept(helpers.ReqHeaderAuthBearer(h.cUser)) +} + +func (h helper) mockPermissions(rules ...*permissions.Rule) { + h.a.NoError(p.Grant( + // TestService we use does not have any backend storage, + context.Background(), + // We want to make sure we did not make a mistake with any of the mocked resources or actions + service.DefaultAccessControl.Whitelist(), + rules..., + )) +} + +// Prepends allow access rule for messaging service for everyone +func (h helper) mockPermissionsWithAccess(rules ...*permissions.Rule) { + rules = append( + rules, + permissions.AllowRule(permissions.EveryoneRoleID, types.MessagingPermissionResource, "access"), + ) + + h.mockPermissions(rules...) +} + +// Set allow permision for test role +func (h helper) allow(r permissions.Resource, o permissions.Operation) { + h.mockPermissions(permissions.AllowRule(h.roleID, r, o)) +} + +// set deny permission for test role +func (h helper) deny(r permissions.Resource, o permissions.Operation) { + h.mockPermissions(permissions.DenyRule(h.roleID, r, o)) +}