mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #38536 This PR moves all logic to create new activities to activity bounded context. The old service and ActivityModule methods are not facades that route to the new activity bounded context. The facades will be removed in a subsequent PR. # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added webhook support for activity events with configurable endpoint and enable/disable settings. * Enhanced automation-initiated activity creation without requiring a user context. * Improved activity service architecture with centralized creation and management. * **Improvements** * Refactored activity creation to use a dedicated service layer for better separation of concerns. * Added support for host-specific and automation-originated activities. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
344 lines
8.2 KiB
Go
344 lines
8.2 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/authz"
|
|
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
|
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/mock"
|
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
|
"github.com/fleetdm/fleet/v4/server/test"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestGetPack(t *testing.T) {
|
|
ds := new(mock.Store)
|
|
svc, ctx := newTestService(t, ds, nil, nil)
|
|
|
|
ds.PackFunc = func(ctx context.Context, id uint) (*fleet.Pack, error) {
|
|
return &fleet.Pack{
|
|
ID: 1,
|
|
TeamIDs: []uint{1},
|
|
}, nil
|
|
}
|
|
|
|
pack, err := svc.GetPack(test.UserContext(ctx, test.UserAdmin), 1)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint(1), pack.ID)
|
|
|
|
_, err = svc.GetPack(test.UserContext(ctx, test.UserNoRoles), 1)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), authz.ForbiddenErrorMessage)
|
|
}
|
|
|
|
func TestNewPackSavesTargets(t *testing.T) {
|
|
ds := new(mock.Store)
|
|
opts := &TestServerOpts{}
|
|
svc, ctx := newTestService(t, ds, nil, nil, opts)
|
|
|
|
ds.NewPackFunc = func(ctx context.Context, pack *fleet.Pack, opts ...fleet.OptionalArg) (*fleet.Pack, error) {
|
|
return pack, nil
|
|
}
|
|
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
|
return &fleet.AppConfig{}, nil
|
|
}
|
|
packPayload := fleet.PackPayload{
|
|
Name: ptr.String("foo"),
|
|
HostIDs: &[]uint{123},
|
|
LabelIDs: &[]uint{456},
|
|
TeamIDs: &[]uint{789},
|
|
}
|
|
pack, err := svc.NewPack(test.UserContext(ctx, test.UserAdmin), packPayload)
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, pack.HostIDs, 1)
|
|
require.Len(t, pack.LabelIDs, 1)
|
|
require.Len(t, pack.TeamIDs, 1)
|
|
assert.Equal(t, uint(123), pack.HostIDs[0])
|
|
assert.Equal(t, uint(456), pack.LabelIDs[0])
|
|
assert.Equal(t, uint(789), pack.TeamIDs[0])
|
|
assert.True(t, ds.NewPackFuncInvoked)
|
|
assert.True(t, opts.ActivityMock.NewActivityFuncInvoked)
|
|
}
|
|
|
|
func TestPacksWithDS(t *testing.T) {
|
|
ds := mysql.CreateMySQLDS(t)
|
|
|
|
cases := []struct {
|
|
name string
|
|
fn func(t *testing.T, ds *mysql.Datastore)
|
|
}{
|
|
{"ListPacks", testPacksListPacks},
|
|
{"DeletePack", testPacksDeletePack},
|
|
{"DeletePackByID", testPacksDeletePackByID},
|
|
{"ApplyPackSpecs", testPacksApplyPackSpecs},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
defer mysql.TruncateTables(t, ds)
|
|
c.fn(t, ds)
|
|
})
|
|
}
|
|
}
|
|
|
|
func testPacksListPacks(t *testing.T, ds *mysql.Datastore) {
|
|
svc, ctx := newTestService(t, ds, nil, nil)
|
|
|
|
queries, err := svc.ListPacks(test.UserContext(ctx, test.UserAdmin), fleet.PackListOptions{IncludeSystemPacks: false})
|
|
require.NoError(t, err)
|
|
assert.Len(t, queries, 0)
|
|
|
|
_, err = ds.NewPack(ctx, &fleet.Pack{
|
|
Name: "foo",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
queries, err = svc.ListPacks(test.UserContext(ctx, test.UserAdmin), fleet.PackListOptions{IncludeSystemPacks: false})
|
|
require.NoError(t, err)
|
|
assert.Len(t, queries, 1)
|
|
}
|
|
|
|
func testPacksDeletePack(t *testing.T, ds *mysql.Datastore) {
|
|
test.AddAllHostsLabel(t, ds)
|
|
|
|
users := createTestUsers(t, ds)
|
|
user := users["admin1@example.com"]
|
|
|
|
type args struct {
|
|
ctx context.Context
|
|
name string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "delete pack that doesn't exist",
|
|
args: args{
|
|
ctx: test.UserContext(context.Background(), &user),
|
|
name: "foo",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
svc, _ := newTestService(t, ds, nil, nil)
|
|
if err := svc.DeletePack(tt.args.ctx, tt.args.name); (err != nil) != tt.wantErr {
|
|
t.Errorf("DeletePack() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func testPacksDeletePackByID(t *testing.T, ds *mysql.Datastore) {
|
|
test.AddAllHostsLabel(t, ds)
|
|
|
|
type args struct {
|
|
ctx context.Context
|
|
id uint
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "cannot delete pack that doesn't exists",
|
|
args: args{
|
|
ctx: test.UserContext(context.Background(), test.UserAdmin),
|
|
id: 123456,
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
svc, _ := newTestService(t, ds, nil, nil)
|
|
if err := svc.DeletePackByID(tt.args.ctx, tt.args.id); (err != nil) != tt.wantErr {
|
|
t.Errorf("DeletePackByID() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func testPacksApplyPackSpecs(t *testing.T, ds *mysql.Datastore) {
|
|
test.AddAllHostsLabel(t, ds)
|
|
|
|
users := createTestUsers(t, ds)
|
|
user := users["admin1@example.com"]
|
|
|
|
type args struct {
|
|
ctx context.Context
|
|
specs []*fleet.PackSpec
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want []*fleet.PackSpec
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "cannot modify global pack",
|
|
args: args{
|
|
ctx: test.UserContext(context.Background(), &user),
|
|
specs: []*fleet.PackSpec{
|
|
{Name: "Foo Pack", Description: "Foo Desc", Platform: "MacOS"},
|
|
{Name: "Bar Pack", Description: "Bar Desc", Platform: "MacOS"},
|
|
},
|
|
},
|
|
want: []*fleet.PackSpec{
|
|
{Name: "Foo Pack", Description: "Foo Desc", Platform: "MacOS"},
|
|
{Name: "Bar Pack", Description: "Bar Desc", Platform: "MacOS"},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "cannot modify team pack",
|
|
args: args{
|
|
ctx: test.UserContext(context.Background(), &user),
|
|
specs: []*fleet.PackSpec{
|
|
{Name: "Test", Description: "Test Desc", Platform: "linux"},
|
|
},
|
|
},
|
|
want: []*fleet.PackSpec{
|
|
{Name: "Test", Description: "Test Desc", Platform: "linux"},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
svc, _ := newTestService(t, ds, nil, nil)
|
|
got, err := svc.ApplyPackSpecs(tt.args.ctx, tt.args.specs)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("ApplyPackSpecs() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
require.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUserIsGitOpsOnly(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
user *fleet.User
|
|
expectedFn func(value bool, err error) bool
|
|
}{
|
|
{
|
|
name: "missing user in context",
|
|
user: nil,
|
|
expectedFn: func(value bool, err error) bool {
|
|
return err != nil && !value
|
|
},
|
|
},
|
|
{
|
|
name: "no roles",
|
|
user: &fleet.User{},
|
|
expectedFn: func(value bool, err error) bool {
|
|
return err != nil && !value
|
|
},
|
|
},
|
|
{
|
|
name: "global gitops",
|
|
user: &fleet.User{
|
|
GlobalRole: ptr.String(fleet.RoleGitOps),
|
|
},
|
|
expectedFn: func(value bool, err error) bool {
|
|
return err == nil && value
|
|
},
|
|
},
|
|
{
|
|
name: "global non-gitops",
|
|
user: &fleet.User{
|
|
GlobalRole: ptr.String(fleet.RoleObserver),
|
|
},
|
|
expectedFn: func(value bool, err error) bool {
|
|
return err == nil && !value
|
|
},
|
|
},
|
|
{
|
|
name: "team gitops",
|
|
user: &fleet.User{
|
|
GlobalRole: nil,
|
|
Teams: []fleet.UserTeam{
|
|
{
|
|
Team: fleet.Team{ID: 1},
|
|
Role: fleet.RoleGitOps,
|
|
},
|
|
},
|
|
},
|
|
expectedFn: func(value bool, err error) bool {
|
|
return err == nil && value
|
|
},
|
|
},
|
|
{
|
|
name: "multiple team gitops",
|
|
user: &fleet.User{
|
|
GlobalRole: nil,
|
|
Teams: []fleet.UserTeam{
|
|
{
|
|
Team: fleet.Team{ID: 1},
|
|
Role: fleet.RoleGitOps,
|
|
},
|
|
{
|
|
Team: fleet.Team{ID: 2},
|
|
Role: fleet.RoleGitOps,
|
|
},
|
|
},
|
|
},
|
|
expectedFn: func(value bool, err error) bool {
|
|
return err == nil && value
|
|
},
|
|
},
|
|
{
|
|
name: "multiple teams, not all gitops",
|
|
user: &fleet.User{
|
|
GlobalRole: nil,
|
|
Teams: []fleet.UserTeam{
|
|
{
|
|
Team: fleet.Team{ID: 1},
|
|
Role: fleet.RoleObserver,
|
|
},
|
|
{
|
|
Team: fleet.Team{ID: 2},
|
|
Role: fleet.RoleGitOps,
|
|
},
|
|
},
|
|
},
|
|
expectedFn: func(value bool, err error) bool {
|
|
return err == nil && !value
|
|
},
|
|
},
|
|
{
|
|
name: "multiple teams, none gitops",
|
|
user: &fleet.User{
|
|
GlobalRole: nil,
|
|
Teams: []fleet.UserTeam{
|
|
{
|
|
Team: fleet.Team{ID: 1},
|
|
Role: fleet.RoleObserver,
|
|
},
|
|
{
|
|
Team: fleet.Team{ID: 2},
|
|
Role: fleet.RoleMaintainer,
|
|
},
|
|
},
|
|
},
|
|
expectedFn: func(value bool, err error) bool {
|
|
return err == nil && !value
|
|
},
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
actual, err := userIsGitOpsOnly(viewer.NewContext(context.Background(), viewer.Viewer{User: tc.user}))
|
|
require.True(t, tc.expectedFn(actual, err))
|
|
})
|
|
}
|
|
}
|