2025-02-26 16:47:05 +00:00
|
|
|
package tests
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
|
|
|
|
"os"
|
2025-02-26 22:20:02 +00:00
|
|
|
"sync"
|
2025-02-26 16:47:05 +00:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/fleetdm/fleet/v4/server/config"
|
2025-08-25 15:41:28 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
2025-02-26 16:47:05 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
|
|
|
"github.com/fleetdm/fleet/v4/server/mdm/android"
|
2025-06-13 00:42:15 +00:00
|
|
|
android_mock "github.com/fleetdm/fleet/v4/server/mdm/android/mock"
|
2025-02-26 16:47:05 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/mdm/android/service"
|
2025-06-13 00:42:15 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/mdm/android/service/androidmgmt"
|
2025-02-26 16:47:05 +00:00
|
|
|
ds_mock "github.com/fleetdm/fleet/v4/server/mock"
|
|
|
|
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
|
|
|
|
"github.com/fleetdm/fleet/v4/server/service/middleware/auth"
|
|
|
|
|
"github.com/fleetdm/fleet/v4/server/service/middleware/endpoint_utils"
|
|
|
|
|
"github.com/fleetdm/fleet/v4/server/service/middleware/log"
|
|
|
|
|
kithttp "github.com/go-kit/kit/transport/http"
|
|
|
|
|
kitlog "github.com/go-kit/log"
|
|
|
|
|
"github.com/gorilla/mux"
|
2025-02-27 21:16:32 +00:00
|
|
|
"github.com/jmoiron/sqlx"
|
2025-02-26 16:47:05 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
"github.com/stretchr/testify/mock"
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
|
"google.golang.org/api/androidmanagement/v1"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
EnterpriseSignupURL = "https://enterprise.google.com/signup/android/email?origin=android&thirdPartyToken=B4D779F1C4DD9A440"
|
|
|
|
|
EnterpriseID = "LC02k5wxw7"
|
|
|
|
|
)
|
|
|
|
|
|
2025-03-13 19:28:52 +00:00
|
|
|
type AndroidDSWithMock struct {
|
|
|
|
|
*mysql.Datastore
|
|
|
|
|
ds_mock.Store
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-25 15:41:28 +00:00
|
|
|
// resolve ambiguity between embedded datastore and mock methods
|
|
|
|
|
func (ds *AndroidDSWithMock) AppConfig(ctx context.Context) (*fleet.AppConfig, error) {
|
|
|
|
|
return ds.Store.AppConfig(ctx) // use mock datastore
|
|
|
|
|
}
|
|
|
|
|
func (ds *AndroidDSWithMock) CreateDeviceTx(ctx context.Context, tx sqlx.ExtContext, device *android.Device) (*android.Device, error) {
|
|
|
|
|
return ds.Datastore.CreateDeviceTx(ctx, tx, device)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ds *AndroidDSWithMock) UpdateDeviceTx(ctx context.Context, tx sqlx.ExtContext, device *android.Device) error {
|
|
|
|
|
return ds.Datastore.UpdateDeviceTx(ctx, tx, device)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ds *AndroidDSWithMock) CreateEnterprise(ctx context.Context, userID uint) (uint, error) {
|
|
|
|
|
return ds.Datastore.CreateEnterprise(ctx, userID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ds *AndroidDSWithMock) GetEnterpriseByID(ctx context.Context, id uint) (*android.EnterpriseDetails, error) {
|
|
|
|
|
return ds.Datastore.GetEnterpriseByID(ctx, id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ds *AndroidDSWithMock) GetEnterpriseBySignupToken(ctx context.Context, signupToken string) (*android.EnterpriseDetails, error) {
|
|
|
|
|
return ds.Datastore.GetEnterpriseBySignupToken(ctx, signupToken)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ds *AndroidDSWithMock) GetEnterprise(ctx context.Context) (*android.Enterprise, error) {
|
|
|
|
|
return ds.Datastore.GetEnterprise(ctx)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ds *AndroidDSWithMock) UpdateEnterprise(ctx context.Context, enterprise *android.EnterpriseDetails) error {
|
|
|
|
|
return ds.Datastore.UpdateEnterprise(ctx, enterprise)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ds *AndroidDSWithMock) DeleteAllEnterprises(ctx context.Context) error {
|
|
|
|
|
return ds.Datastore.DeleteAllEnterprises(ctx)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ds *AndroidDSWithMock) DeleteOtherEnterprises(ctx context.Context, id uint) error {
|
|
|
|
|
return ds.Datastore.DeleteOtherEnterprises(ctx, id)
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-26 16:47:05 +00:00
|
|
|
type WithServer struct {
|
|
|
|
|
suite.Suite
|
2025-02-27 20:19:15 +00:00
|
|
|
Svc android.Service
|
2025-03-13 19:28:52 +00:00
|
|
|
DS AndroidDSWithMock
|
2025-02-27 20:19:15 +00:00
|
|
|
FleetSvc mockService
|
|
|
|
|
Server *httptest.Server
|
|
|
|
|
Token string
|
2025-02-26 22:20:02 +00:00
|
|
|
|
|
|
|
|
AppConfig fleet.AppConfig
|
|
|
|
|
AppConfigMu sync.Mutex
|
|
|
|
|
|
2025-06-13 00:42:15 +00:00
|
|
|
AndroidAPIClient android_mock.Client
|
2025-02-26 16:47:05 +00:00
|
|
|
ProxyCallbackURL string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ts *WithServer) SetupSuite(t *testing.T, dbName string) {
|
2025-03-13 19:28:52 +00:00
|
|
|
ts.DS.Datastore = CreateNamedMySQLDS(t, dbName)
|
2025-02-26 22:20:02 +00:00
|
|
|
ts.CreateCommonDSMocks()
|
2025-02-26 16:47:05 +00:00
|
|
|
|
2025-06-13 00:42:15 +00:00
|
|
|
ts.AndroidAPIClient = android_mock.Client{}
|
2025-02-26 16:47:05 +00:00
|
|
|
ts.createCommonProxyMocks(t)
|
|
|
|
|
|
|
|
|
|
logger := kitlog.NewLogfmtLogger(os.Stdout)
|
2025-08-14 19:47:23 +00:00
|
|
|
svc, err := service.NewServiceWithClient(logger, &ts.DS, &ts.AndroidAPIClient, &ts.FleetSvc, "test-private-key")
|
2025-02-26 16:47:05 +00:00
|
|
|
require.NoError(t, err)
|
2025-02-26 22:20:02 +00:00
|
|
|
ts.Svc = svc
|
2025-02-26 16:47:05 +00:00
|
|
|
|
2025-02-27 20:19:15 +00:00
|
|
|
ts.Server = runServerForTests(t, logger, &ts.FleetSvc, svc)
|
2025-02-26 16:47:05 +00:00
|
|
|
}
|
|
|
|
|
|
2025-02-26 22:20:02 +00:00
|
|
|
func (ts *WithServer) CreateCommonDSMocks() {
|
2025-03-13 19:28:52 +00:00
|
|
|
ts.DS.AppConfigFunc = func(_ context.Context) (*fleet.AppConfig, error) {
|
2025-02-26 22:20:02 +00:00
|
|
|
// Create a copy to prevent race conditions
|
|
|
|
|
ts.AppConfigMu.Lock()
|
|
|
|
|
appConfigCopy := ts.AppConfig
|
|
|
|
|
ts.AppConfigMu.Unlock()
|
|
|
|
|
return &appConfigCopy, nil
|
2025-02-26 16:47:05 +00:00
|
|
|
}
|
2025-03-13 19:28:52 +00:00
|
|
|
ts.DS.SetAndroidEnabledAndConfiguredFunc = func(_ context.Context, configured bool) error {
|
2025-02-26 22:20:02 +00:00
|
|
|
ts.AppConfigMu.Lock()
|
2025-02-26 16:47:05 +00:00
|
|
|
ts.AppConfig.MDM.AndroidEnabledAndConfigured = configured
|
2025-02-26 22:20:02 +00:00
|
|
|
ts.AppConfigMu.Unlock()
|
2025-02-26 16:47:05 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
2025-03-13 19:28:52 +00:00
|
|
|
ts.DS.UserOrDeletedUserByIDFunc = func(_ context.Context, id uint) (*fleet.User, error) {
|
2025-02-27 20:19:15 +00:00
|
|
|
return &fleet.User{ID: id}, nil
|
|
|
|
|
}
|
2025-03-13 19:28:52 +00:00
|
|
|
ts.DS.GetAllMDMConfigAssetsByNameFunc = func(ctx context.Context, assetNames []fleet.MDMAssetName,
|
2025-08-18 16:31:53 +00:00
|
|
|
queryerContext sqlx.QueryerContext,
|
|
|
|
|
) (map[fleet.MDMAssetName]fleet.MDMConfigAsset, error) {
|
2025-02-27 21:16:32 +00:00
|
|
|
result := make(map[fleet.MDMAssetName]fleet.MDMConfigAsset, len(assetNames))
|
|
|
|
|
for _, name := range assetNames {
|
|
|
|
|
result[name] = fleet.MDMConfigAsset{Value: []byte("value")}
|
|
|
|
|
}
|
|
|
|
|
return result, nil
|
|
|
|
|
}
|
2025-03-13 19:28:52 +00:00
|
|
|
ts.DS.InsertOrReplaceMDMConfigAssetFunc = func(ctx context.Context, asset fleet.MDMConfigAsset) error {
|
2025-02-27 21:16:32 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
2025-03-13 19:28:52 +00:00
|
|
|
ts.DS.DeleteMDMConfigAssetsByNameFunc = func(ctx context.Context, assetNames []fleet.MDMAssetName) error {
|
2025-02-27 21:16:32 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
2025-03-13 19:28:52 +00:00
|
|
|
ts.DS.BulkSetAndroidHostsUnenrolledFunc = func(ctx context.Context) error {
|
2025-03-05 20:47:06 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
2025-02-26 16:47:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ts *WithServer) createCommonProxyMocks(t *testing.T) {
|
2025-06-13 00:42:15 +00:00
|
|
|
ts.AndroidAPIClient.InitCommonMocks()
|
|
|
|
|
ts.AndroidAPIClient.SignupURLsCreateFunc = func(_ context.Context, _, callbackURL string) (*android.SignupDetails, error) {
|
2025-02-26 16:47:05 +00:00
|
|
|
ts.ProxyCallbackURL = callbackURL
|
|
|
|
|
return &android.SignupDetails{
|
|
|
|
|
Url: EnterpriseSignupURL,
|
|
|
|
|
Name: "signupUrls/Cb08124d0999c464f",
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
2025-06-13 00:42:15 +00:00
|
|
|
ts.AndroidAPIClient.EnterprisesCreateFunc = func(_ context.Context, _ androidmgmt.EnterprisesCreateRequest) (androidmgmt.EnterprisesCreateResponse, error) {
|
|
|
|
|
return androidmgmt.EnterprisesCreateResponse{
|
|
|
|
|
EnterpriseName: "enterprises/" + EnterpriseID,
|
|
|
|
|
TopicName: "projects/android/topics/ae98ed130-5ce2-4ddb-a90a-191ec76976d5",
|
|
|
|
|
}, nil
|
2025-02-26 16:47:05 +00:00
|
|
|
}
|
2025-06-13 00:42:15 +00:00
|
|
|
ts.AndroidAPIClient.EnterprisesPoliciesPatchFunc = func(_ context.Context, policyName string, _ *androidmanagement.Policy) error {
|
|
|
|
|
assert.Contains(t, policyName, EnterpriseID)
|
2025-02-26 16:47:05 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
2025-06-13 00:42:15 +00:00
|
|
|
ts.AndroidAPIClient.EnterpriseDeleteFunc = func(_ context.Context, enterpriseName string) error {
|
|
|
|
|
assert.Equal(t, "enterprises/"+EnterpriseID, enterpriseName)
|
2025-02-26 16:47:05 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ts *WithServer) TearDownSuite() {
|
2025-08-25 15:41:28 +00:00
|
|
|
ts.DS.Datastore.Close()
|
2025-02-26 16:47:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type mockService struct {
|
|
|
|
|
mock.Mock
|
|
|
|
|
fleet.Service
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *mockService) GetSessionByKey(ctx context.Context, sessionKey string) (*fleet.Session, error) {
|
|
|
|
|
return &fleet.Session{UserID: 1}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *mockService) UserUnauthorized(ctx context.Context, userId uint) (*fleet.User, error) {
|
|
|
|
|
return &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-27 20:19:15 +00:00
|
|
|
func (m *mockService) NewActivity(ctx context.Context, user *fleet.User, details fleet.ActivityDetails) error {
|
|
|
|
|
return m.Called(ctx, user, details).Error(0)
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-26 16:47:05 +00:00
|
|
|
func runServerForTests(t *testing.T, logger kitlog.Logger, fleetSvc fleet.Service, androidSvc android.Service) *httptest.Server {
|
|
|
|
|
fleetAPIOptions := []kithttp.ServerOption{
|
|
|
|
|
kithttp.ServerBefore(
|
|
|
|
|
kithttp.PopulateRequestContext,
|
|
|
|
|
auth.SetRequestsContexts(fleetSvc),
|
|
|
|
|
),
|
|
|
|
|
kithttp.ServerErrorHandler(&endpoint_utils.ErrorHandler{Logger: logger}),
|
|
|
|
|
kithttp.ServerErrorEncoder(endpoint_utils.EncodeError),
|
|
|
|
|
kithttp.ServerAfter(
|
|
|
|
|
kithttp.SetContentType("application/json; charset=utf-8"),
|
|
|
|
|
log.LogRequestEnd(logger),
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r := mux.NewRouter()
|
|
|
|
|
service.GetRoutes(fleetSvc, androidSvc)(r, fleetAPIOptions)
|
|
|
|
|
rootMux := http.NewServeMux()
|
|
|
|
|
rootMux.HandleFunc("/api/", r.ServeHTTP)
|
|
|
|
|
|
|
|
|
|
server := httptest.NewUnstartedServer(rootMux)
|
|
|
|
|
serverConfig := config.ServerConfig{}
|
|
|
|
|
server.Config = serverConfig.DefaultHTTPServer(testCtx(), rootMux)
|
|
|
|
|
require.NotZero(t, server.Config.WriteTimeout)
|
|
|
|
|
server.Config.Handler = rootMux
|
|
|
|
|
server.Start()
|
|
|
|
|
t.Cleanup(func() {
|
|
|
|
|
server.Close()
|
|
|
|
|
})
|
|
|
|
|
return server
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testCtx() context.Context {
|
|
|
|
|
return context.Background()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func CreateNamedMySQLDS(t *testing.T, name string) *mysql.Datastore {
|
|
|
|
|
if _, ok := os.LookupEnv("MYSQL_TEST"); !ok {
|
|
|
|
|
t.Skip("MySQL tests are disabled")
|
|
|
|
|
}
|
2025-08-25 15:41:28 +00:00
|
|
|
// use the standard Fleet datastore for Android integration tests
|
|
|
|
|
return mysql.CreateMySQLDS(t)
|
2025-02-26 16:47:05 +00:00
|
|
|
}
|