package proto_test

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"testing"

	merrors "github.com/asim/go-micro/v3/errors"
	"github.com/asim/go-micro/v3/metadata"
	ocislog "github.com/owncloud/ocis/ocis-pkg/log"
	"github.com/owncloud/ocis/ocis-pkg/middleware"
	"github.com/owncloud/ocis/ocis-pkg/service/grpc"
	"github.com/owncloud/ocis/settings/pkg/config"
	"github.com/owncloud/ocis/settings/pkg/proto/v0"
	svc "github.com/owncloud/ocis/settings/pkg/service/v0"
	store "github.com/owncloud/ocis/settings/pkg/store/filesystem"
	"github.com/stretchr/testify/assert"
)

var (
	service           grpc.Service
	handler           svc.Service
	bundleService     proto.BundleService
	valueService      proto.ValueService
	roleService       proto.RoleService
	permissionService proto.PermissionService

	testAccountID = "e8a7f56b-10ce-4f67-b67f-eca40aa0ef26"

	settingsStub = []*proto.Setting{
		{
			Id:          "336c4db1-5062-4931-990f-d88e6b02cb02",
			DisplayName: "dummy setting",
			Name:        "dummy-setting",
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
			Value: &proto.Setting_IntValue{
				IntValue: &proto.Int{
					Default: 42,
				},
			},
			Description: "dummy setting",
		},
	}

	//optionsForListStub for choice list settings
	optionsForListStub = []*proto.ListOption{
		{
			Value: &proto.ListOptionValue{
				Option: &proto.ListOptionValue_StringValue{StringValue: "list option string value"},
			},
			Default:      true,
			DisplayValue: "a string value",
		},
		{
			Value: &proto.ListOptionValue{
				Option: &proto.ListOptionValue_IntValue{IntValue: 123},
			},
			Default:      true,
			DisplayValue: "a int value",
		},
	}

	//MultiChoiceList
	multipleChoiceSettingStub = proto.MultiChoiceList{
		Options: optionsForListStub,
	}

	//SingleChoiceList
	singleChoiceSettingStub = proto.SingleChoiceList{
		Options: optionsForListStub,
	}

	complexSettingsStub = []*proto.Setting{
		{
			Name:        "int",
			Id:          "4e00633d-5373-4df4-9299-1c9ed9c3ebed",
			DisplayName: "an integer value",
			Description: "with some description",
			Value: &proto.Setting_IntValue{
				IntValue: &proto.Int{
					Default:     1,
					Min:         1,
					Max:         124,
					Step:        1,
					Placeholder: "Int value",
				},
			},
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
		},
		{
			Name:        "string",
			Id:          "f792acb4-9f09-4fa8-92d3-4a0d0a6ca721",
			DisplayName: "a string value",
			Description: "with some description",
			Value: &proto.Setting_StringValue{
				StringValue: &proto.String{
					Default:     "thedefaultvalue",
					Required:    false,
					MinLength:   2,
					MaxLength:   255,
					Placeholder: "a string value",
				},
			},
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
		},
		{
			Name:        "bool",
			Id:          "6ef9268c-f0bd-48a7-a0a0-3ba3ee42b5cc",
			DisplayName: "a bool value",
			Description: "with some description",
			Value: &proto.Setting_BoolValue{
				BoolValue: &proto.Bool{
					Default: false,
					Label:   "bool setting",
				},
			},
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
		},
		{
			Name:        "multipleChoice",
			Id:          "905da88c-3be0-42c2-a8b2-e6bcd9976b2d",
			DisplayName: "a multiple choice setting",
			Description: "with some description",
			Value: &proto.Setting_MultiChoiceValue{
				MultiChoiceValue: &multipleChoiceSettingStub,
			},
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
		},
		{
			Name:        "singleChoice",
			Id:          "5bf4de47-57cc-4705-a456-4fcf39673994",
			DisplayName: "a single choice setting",
			Description: "with some description",
			Value: &proto.Setting_SingleChoiceValue{
				SingleChoiceValue: &singleChoiceSettingStub,
			},
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
		},
	}

	bundleStub = proto.Bundle{
		Name:        "test",
		Id:          "b1b8c9d0-fb3c-4e12-b868-5a8508218d2e",
		DisplayName: "bundleDisplayName",
		Extension:   "testExtension",
		Type:        proto.Bundle_TYPE_DEFAULT,
		Settings:    complexSettingsStub,
		Resource: &proto.Resource{
			Type: proto.Resource_TYPE_SYSTEM,
		},
	}
)

const dataPath = "/var/tmp/grpc-tests-ocis-settings"

func init() {
	service = grpc.NewService(
		grpc.Namespace("com.owncloud.api"),
		grpc.Name("settings"),
		grpc.Address("localhost:9992"),
	)

	cfg := config.New()
	cfg.Service.DataPath = dataPath
	handler = svc.NewService(cfg, ocislog.NewLogger(ocislog.Color(true), ocislog.Pretty(true)))
	err := proto.RegisterBundleServiceHandler(service.Server(), handler)
	if err != nil {
		log.Fatalf("could not register BundleServiceHandler: %v", err)
	}
	err = proto.RegisterValueServiceHandler(service.Server(), handler)
	if err != nil {
		log.Fatalf("could not register ValueServiceHandler: %v", err)
	}
	err = proto.RegisterRoleServiceHandler(service.Server(), handler)
	if err != nil {
		log.Fatalf("could not register RoleServiceHandler: %v", err)
	}
	err = proto.RegisterPermissionServiceHandler(service.Server(), handler)
	if err != nil {
		log.Fatalf("could not register PermissionServiceHandler: %v", err)
	}

	if err = service.Server().Start(); err != nil {
		log.Fatalf("could not start server: %v", err)
	}

	client := service.Client()
	bundleService = proto.NewBundleService("com.owncloud.api.settings", client)
	valueService = proto.NewValueService("com.owncloud.api.settings", client)
	roleService = proto.NewRoleService("com.owncloud.api.settings", client)
	permissionService = proto.NewPermissionService("com.owncloud.api.settings", client)
}

func setup() func() {
	handler.RegisterDefaultRoles()
	return func() {
		if err := os.RemoveAll(dataPath); err != nil {
			log.Printf("could not delete data root: %s", dataPath)
		} else {
			log.Println("data root deleted")
		}
	}
}

/**
testing that saving a settings bundle and retrieving it again works correctly
using various setting bundle properties
*/
func TestBundleInputValidation(t *testing.T) {
	var scenarios = []struct {
		name          string
		bundleName    string
		displayName   string
		extensionName string
		expectedError error
	}{
		{
			"ASCII",
			"bundle-name",
			"simple-bundle-key",
			"simple-extension-name",
			nil,
		},
		{
			"UTF validation on bundle name",
			"सिम्प्ले-bundle-name",
			"सिम्प्ले-display-name",
			"सिम्प्ले-extension-name",
			merrors.New("ocis-settings", "extension: must be in a valid format; name: must be in a valid format.", http.StatusBadRequest),
		},
		{
			"UTF validation on display name",
			"सिम्प्ले-bundle-name",
			"सिम्प्ले-display-name",
			"simple-extension-name",
			merrors.New("ocis-settings", "name: must be in a valid format.", http.StatusBadRequest),
		},
		{
			"extension name with ../ in the name",
			"bundle-name",
			"simple-display-name",
			"../folder-a-level-higher-up",
			merrors.New("ocis-settings", "extension: must be in a valid format.", http.StatusBadRequest),
		},
		{
			"extension name with \\ in the name",
			"bundle-name",
			"simple-display-name",
			"\\",
			merrors.New("ocis-settings", "extension: must be in a valid format.", http.StatusBadRequest),
		},
		{
			"spaces are disallowed in bundle names",
			"bundle name",
			"simple display name",
			"simple extension name",
			merrors.New("ocis-settings", "extension: must be in a valid format; name: must be in a valid format.", http.StatusBadRequest),
		},
		{
			"spaces are allowed in display names",
			"bundle-name",
			"simple display name",
			"simple-extension-name",
			nil,
		},
		{
			"extension missing",
			"bundle-name",
			"simple-display-name",
			"",
			merrors.New("ocis-settings", "extension: cannot be blank.", http.StatusBadRequest),
		},
		{
			"display name missing",
			"bundleName",
			"",
			"simple-extension-name",
			merrors.New("ocis-settings", "display_name: cannot be blank.", http.StatusBadRequest),
		},
	}
	for _, scenario := range scenarios {
		t.Run(scenario.name, func(t *testing.T) {
			teardown := setup()
			defer teardown()

			ctx := metadata.Set(context.Background(), middleware.AccountID, testAccountID)
			ctx = metadata.Set(ctx, middleware.RoleIDs, getRoleIDAsJSON(svc.BundleUUIDRoleAdmin))

			bundle := proto.Bundle{
				Name:        scenario.bundleName,
				Extension:   scenario.extensionName,
				DisplayName: scenario.displayName,
				Type:        proto.Bundle_TYPE_DEFAULT,
				Resource: &proto.Resource{
					Type: proto.Resource_TYPE_SYSTEM,
				},
				Settings: settingsStub,
			}
			createRequest := proto.SaveBundleRequest{
				Bundle: &bundle,
			}

			cresponse, err := bundleService.SaveBundle(ctx, &createRequest)
			if err != nil || scenario.expectedError != nil {
				t.Log(err)
				assert.Equal(t, scenario.expectedError, err)
			} else {
				assert.Equal(t, scenario.extensionName, cresponse.Bundle.Extension)
				assert.Equal(t, scenario.displayName, cresponse.Bundle.DisplayName)

				// we want to test input validation, so just allow the request permission-wise
				setFullReadWriteOnBundleForAdmin(ctx, t, cresponse.Bundle.Id)

				getRequest := proto.GetBundleRequest{BundleId: cresponse.Bundle.Id}
				getResponse, err := bundleService.GetBundle(ctx, &getRequest)
				assert.NoError(t, err)
				if err == nil {
					assert.Equal(t, scenario.displayName, getResponse.Bundle.DisplayName)
				}
			}
		})
	}
}

func TestSaveBundleWithoutSettings(t *testing.T) {
	teardown := setup()
	defer teardown()

	ctx := metadata.Set(context.Background(), middleware.AccountID, testAccountID)
	ctx = metadata.Set(ctx, middleware.RoleIDs, getRoleIDAsJSON(svc.BundleUUIDRoleAdmin))

	createRequest := proto.SaveBundleRequest{
		Bundle: &proto.Bundle{
			DisplayName: "Alice's Bundle",
		},
	}
	response, err := bundleService.SaveBundle(ctx, &createRequest)
	assert.Error(t, err)
	assert.Nil(t, response)
	assert.Equal(t, merrors.New("ocis-settings", "extension: cannot be blank; name: cannot be blank; settings: cannot be blank.", http.StatusBadRequest), err)
}

func TestGetBundleOfABundleSavedWithoutPermissions(t *testing.T) {
	teardown := setup()
	defer teardown()

	ctx := metadata.Set(context.Background(), middleware.AccountID, testAccountID)
	ctx = metadata.Set(ctx, middleware.RoleIDs, getRoleIDAsJSON(svc.BundleUUIDRoleAdmin))

	saveRequest := proto.SaveBundleRequest{
		Bundle: &bundleStub,
	}
	saveResponse, err := bundleService.SaveBundle(ctx, &saveRequest)
	assert.NoError(t, err)
	assert.Equal(t, bundleStub.Id, saveResponse.Bundle.Id)

	getRequest := proto.GetBundleRequest{BundleId: bundleStub.Id}
	getResponse, err := bundleService.GetBundle(ctx, &getRequest)
	assert.Empty(t, getResponse)

	assert.Equal(t, merrors.New("ocis-settings", "could not read bundle: b1b8c9d0-fb3c-4e12-b868-5a8508218d2e", http.StatusNotFound), err)
}

func TestGetBundleHavingFullPermissionsOnAnotherRole(t *testing.T) {
	teardown := setup()
	defer teardown()

	ctx := metadata.Set(context.Background(), middleware.AccountID, testAccountID)
	ctx = metadata.Set(ctx, middleware.RoleIDs, getRoleIDAsJSON(svc.BundleUUIDRoleAdmin))

	saveRequest := proto.SaveBundleRequest{
		Bundle: &bundleStub,
	}
	saveResponse, err := bundleService.SaveBundle(ctx, &saveRequest)
	assert.NoError(t, err)
	assert.Equal(t, bundleStub.Id, saveResponse.Bundle.Id)

	setFullReadWriteOnBundleForAdmin(ctx, t, bundleStub.Id)

	ctx = metadata.Set(ctx, middleware.RoleIDs, getRoleIDAsJSON(svc.BundleUUIDRoleUser))
	getRequest := proto.GetBundleRequest{BundleId: bundleStub.Id}
	getResponse, err := bundleService.GetBundle(ctx, &getRequest)
	assert.Empty(t, getResponse)

	assert.Equal(t, merrors.New("ocis-settings", "could not read bundle: b1b8c9d0-fb3c-4e12-b868-5a8508218d2e", http.StatusNotFound), err)
}

/**
testing that setting getting and listing a settings bundle works correctly with a set of setting definitions
*/
func TestSaveAndGetBundle(t *testing.T) {
	teardown := setup()
	defer teardown()

	ctx := metadata.Set(context.Background(), middleware.AccountID, testAccountID)
	ctx = metadata.Set(ctx, middleware.RoleIDs, getRoleIDAsJSON(svc.BundleUUIDRoleAdmin))

	saveRequest := proto.SaveBundleRequest{
		Bundle: &bundleStub,
	}

	// assert that SaveBundle returns the same bundle as we have sent
	saveResponse, err := bundleService.SaveBundle(ctx, &saveRequest)
	assert.NoError(t, err)
	receivedBundle, _ := json.Marshal(saveResponse.Bundle.Settings)
	expectedBundle, _ := json.Marshal(&bundleStub.Settings)
	assert.Equal(t, receivedBundle, expectedBundle)

	// set full permissions for getting the created bundle
	setFullReadWriteOnBundleForAdmin(ctx, t, saveResponse.Bundle.Id)

	//assert that GetBundle returns the same bundle as saved
	getRequest := proto.GetBundleRequest{BundleId: saveResponse.Bundle.Id}
	getResponse, err := bundleService.GetBundle(ctx, &getRequest)
	assert.NoError(t, err)
	if err == nil {
		receivedBundle, _ = json.Marshal(getResponse.Bundle.Settings)
		assert.Equal(t, expectedBundle, receivedBundle)
	}
}

/**
testing that saving a value works and can be retrieved again
*/
func TestSaveGetIntValue(t *testing.T) {
	tests := []struct {
		name  string
		value proto.Value_IntValue
	}{
		{
			name:  "simple int",
			value: proto.Value_IntValue{IntValue: 43},
		},
		{
			name:  "negative",
			value: proto.Value_IntValue{IntValue: -42},
			// no validation for SaveValue https://github.com/owncloud/ocis/issues/1445
		},
		{
			name:  "less than Min",
			value: proto.Value_IntValue{IntValue: 0},
			// no validation for SaveValue https://github.com/owncloud/ocis/issues/1445
		},
		{
			name:  "more than Max",
			value: proto.Value_IntValue{IntValue: 128},
			// no validation for SaveValue https://github.com/owncloud/ocis/issues/1445
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			teardown := setup()
			defer teardown()

			ctx := metadata.Set(context.Background(), middleware.AccountID, testAccountID)
			ctx = metadata.Set(ctx, middleware.RoleIDs, getRoleIDAsJSON(svc.BundleUUIDRoleAdmin))

			saveResponse, err := bundleService.SaveBundle(ctx, &proto.SaveBundleRequest{
				Bundle: &bundleStub,
			})
			assert.NoError(t, err)

			saveValueResponse, err := valueService.SaveValue(ctx, &proto.SaveValueRequest{
				Value: &proto.Value{
					BundleId:    saveResponse.Bundle.Id,
					SettingId:   "4e00633d-5373-4df4-9299-1c9ed9c3ebed", //setting id of the int setting
					AccountUuid: "047d31b0-219a-47a4-8ee5-c5fa3802a3c2",
					Value:       &tt.value,
					Resource: &proto.Resource{
						Type: 0,
						Id:   ".",
					},
				},
			})
			assert.NoError(t, err)
			assert.Equal(t, tt.value.IntValue, saveValueResponse.Value.Value.GetIntValue())

			getValueResponse, err := valueService.GetValue(
				ctx, &proto.GetValueRequest{Id: saveValueResponse.Value.Value.Id},
			)
			assert.NoError(t, err)
			assert.Equal(t, tt.value.IntValue, getValueResponse.Value.Value.GetIntValue())
		})
	}
}

/**
try to save a wrong type of the value
no validation for SaveValue https://github.com/owncloud/ocis/issues/1445
*/
func TestSaveGetIntValueIntoString(t *testing.T) {
	teardown := setup()
	defer teardown()

	ctx := metadata.Set(context.Background(), middleware.AccountID, testAccountID)
	ctx = metadata.Set(ctx, middleware.RoleIDs, getRoleIDAsJSON(svc.BundleUUIDRoleAdmin))

	saveResponse, err := bundleService.SaveBundle(ctx, &proto.SaveBundleRequest{
		Bundle: &bundleStub,
	})
	assert.NoError(t, err)

	saveValueResponse, err := valueService.SaveValue(ctx, &proto.SaveValueRequest{
		Value: &proto.Value{
			BundleId:    saveResponse.Bundle.Id,
			SettingId:   "f792acb4-9f09-4fa8-92d3-4a0d0a6ca721", //setting id of the string setting
			AccountUuid: "047d31b0-219a-47a4-8ee5-c5fa3802a3c2",
			Value:       &proto.Value_StringValue{StringValue: "forty two"},
			Resource: &proto.Resource{
				Type: 0,
				Id:   ".",
			},
		},
	})
	assert.NoError(t, err)
	assert.Equal(t, "forty two", saveValueResponse.Value.Value.GetStringValue())

	getValueResponse, err := valueService.GetValue(
		ctx, &proto.GetValueRequest{Id: saveValueResponse.Value.Value.Id},
	)
	assert.NoError(t, err)
	assert.Equal(t, "forty two", getValueResponse.Value.Value.GetStringValue())
}

// no plausibility check in settings of bundle https://github.com/owncloud/ocis/issues/1455
func TestSaveBundleWithInvalidSettings(t *testing.T) {
	var tests = []proto.Setting{
		{
			Name: "intValue",
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
			Description: "intValue default is out of range",
			Value: &proto.Setting_IntValue{
				IntValue: &proto.Int{
					Default: 30,
					Min:     10,
					Max:     20,
				},
			},
		},
		{
			Name: "intValue",
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
			Description: "intValue min gt max",
			Value: &proto.Setting_IntValue{
				IntValue: &proto.Int{
					Default: 100,
					Min:     100,
					Max:     20,
				},
			},
		},
		{
			Name: "intValue",
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
			Description: "intValue step gt max-min",
			Value: &proto.Setting_IntValue{
				IntValue: &proto.Int{
					Min:  10,
					Max:  20,
					Step: 100,
				},
			},
		},
		{
			Name: "intValue",
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
			Description: "intValue step eq 0",
			Value: &proto.Setting_IntValue{
				IntValue: &proto.Int{
					Min:  10,
					Max:  20,
					Step: 0,
				},
			},
		},
		{
			Name: "intValue",
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
			Description: "intValue step lt 0",
			Value: &proto.Setting_IntValue{
				IntValue: &proto.Int{
					Min:  10,
					Max:  20,
					Step: -10,
				},
			},
		},
		{
			Name: "stringValue",
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
			Description: "stringValue MinLength gt MaxLength",
			Value: &proto.Setting_StringValue{
				StringValue: &proto.String{
					MinLength: 255,
					MaxLength: 1,
				},
			},
		},
		{
			Name: "stringValue",
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
			Description: "stringValue MaxLength eq 0",
			Value: &proto.Setting_StringValue{
				StringValue: &proto.String{
					MaxLength: 0,
				},
			},
		},
		{
			Name: "stringValue",
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
			Description: "stringValue MinLength lt 0",
			Value: &proto.Setting_StringValue{
				StringValue: &proto.String{
					MinLength: -1,
				},
			},
		},
		{
			Name: "stringValue",
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
			Description: "stringValue MaxLength lt 0",
			Value: &proto.Setting_StringValue{
				StringValue: &proto.String{
					MaxLength: -1,
				},
			},
		},
		{
			Name: "multiChoiceValue",
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
			Description: "multiChoice multiple options are default",
			Value: &proto.Setting_MultiChoiceValue{
				MultiChoiceValue: &proto.MultiChoiceList{
					Options: []*proto.ListOption{
						{
							Value: &proto.ListOptionValue{
								Option: &proto.ListOptionValue_IntValue{IntValue: 1},
							},
							Default: true,
						},
						{
							Value: &proto.ListOptionValue{
								Option: &proto.ListOptionValue_IntValue{IntValue: 2},
							},
							Default: true,
						},
					},
				},
			},
		},
		{
			Name: "singleChoiceValue",
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_USER,
			},
			Description: "singleChoice multiple options are default",
			Value: &proto.Setting_SingleChoiceValue{
				SingleChoiceValue: &proto.SingleChoiceList{
					Options: []*proto.ListOption{
						{
							Value: &proto.ListOptionValue{
								Option: &proto.ListOptionValue_IntValue{IntValue: 1},
							},
							Default: true,
						},
						{
							Value: &proto.ListOptionValue{
								Option: &proto.ListOptionValue_IntValue{IntValue: 2},
							},
							Default: true,
						},
					},
				},
			},
		},
	}

	for index := range tests {
		t.Run(tests[index].Name, func(t *testing.T) {
			teardown := setup()
			defer teardown()

			ctx := metadata.Set(context.Background(), middleware.AccountID, testAccountID)
			ctx = metadata.Set(ctx, middleware.RoleIDs, getRoleIDAsJSON(svc.BundleUUIDRoleAdmin))

			var settings []*proto.Setting

			settings = append(settings, &tests[index])
			bundle := proto.Bundle{
				Name:        "bundle",
				Extension:   "bundleExtension",
				DisplayName: "bundledisplayname",
				Type:        proto.Bundle_TYPE_DEFAULT,
				Resource: &proto.Resource{
					Type: proto.Resource_TYPE_SYSTEM,
				},
				Settings: settings,
			}
			saveRequest := proto.SaveBundleRequest{
				Bundle: &bundle,
			}

			//assert that SaveBundle returns the same bundle as we have sent there
			saveResponse, err := bundleService.SaveBundle(ctx, &saveRequest)
			assert.NoError(t, err)
			receivedBundle, _ := json.Marshal(saveResponse.Bundle.Settings)
			expectedBundle, _ := json.Marshal(&bundle.Settings)
			assert.Equal(t, expectedBundle, receivedBundle)
		})
	}
}

func TestGetBundleNoSideEffectsOnDisk(t *testing.T) {
	teardown := setup()
	defer teardown()

	ctx := metadata.Set(context.Background(), middleware.AccountID, testAccountID)
	ctx = metadata.Set(ctx, middleware.RoleIDs, getRoleIDAsJSON(svc.BundleUUIDRoleAdmin))

	getRequest := proto.GetBundleRequest{BundleId: "non-existing-bundle"}

	_, _ = bundleService.GetBundle(ctx, &getRequest)
	assert.NoDirExists(t, store.Name+"/bundles/non-existing-bundle")
	assert.NoFileExists(t, store.Name+"/bundles/non-existing-bundle/not-existing-bundle.json")
}

// TODO non-deterministic. Fix.
func TestCreateRoleAndAssign(t *testing.T) {
	teardown := setup()
	defer teardown()

	ctx := metadata.Set(context.Background(), middleware.AccountID, testAccountID)
	ctx = metadata.Set(ctx, middleware.RoleIDs, getRoleIDAsJSON(svc.BundleUUIDRoleAdmin))

	res, err := bundleService.SaveBundle(ctx, &proto.SaveBundleRequest{
		Bundle: &proto.Bundle{
			Type:        proto.Bundle_TYPE_ROLE,
			DisplayName: "test role - update",
			Name:        "TEST_ROLE",
			Extension:   "ocis-settings",
			Settings: []*proto.Setting{
				{
					Name: "settingName",
					Resource: &proto.Resource{
						Id:   settingsStub[0].Id,
						Type: proto.Resource_TYPE_SETTING,
					},
					Value: &proto.Setting_PermissionValue{
						PermissionValue: &proto.Permission{
							Operation:  proto.Permission_OPERATION_UPDATE,
							Constraint: proto.Permission_CONSTRAINT_OWN,
						},
					},
				},
			},
			Resource: &proto.Resource{
				Type: proto.Resource_TYPE_SYSTEM,
			},
		},
	})
	if err == nil {
		_, err = roleService.AssignRoleToUser(ctx, &proto.AssignRoleToUserRequest{
			AccountUuid: "4c510ada-c86b-4815-8820-42cdf82c3d51",
			RoleId:      res.Bundle.Id,
		})
		if err != nil {
			fmt.Println(err)
		}
		assert.NoError(t, err)
	}
}

// // TODO this tests are non-deterministic at least on my machine. Find a way to make them deterministic.
// func TestListBundlesOnAuthorizedUser(t *testing.T) {
// 	client := service.Client()
// 	client2 := service.Client()
// 	cl := proto.NewBundleService("com.owncloud.api.settings", client)
// 	rc := proto.NewRoleService("com.owncloud.api.settings", client2)

// 	_, err := cl.SaveBundle(context.Background(), &proto.SaveBundleRequest{
// 		Bundle: &proto.Bundle{
// 			DisplayName: "Alice's Bundle",
// 			Name:        "bundle1",
// 			Extension:   "extension1",
// 			Resource: &proto.Resource{
// 				Type: proto.Resource_TYPE_BUNDLE,
// 			},
// 			Type:     proto.Bundle_TYPE_DEFAULT,
// 			Settings: settingsStub,
// 		},
// 	})
// 	assert.NoError(t, err)

// res, err := cl.SaveBundle(context.Background(), &proto.SaveBundleRequest{
// 	Bundle: &proto.Bundle{
// 		// Id:          "f36db5e6-a03c-40df-8413-711c67e40b47", // bug: providing the ID ignores its value for the filename.
// 		Type:        proto.Bundle_TYPE_ROLE,
// 		DisplayName: "test role - update",
// 		Name:        "TEST_ROLE",
// 		Extension:   "ocis-settings",
// 		Settings: []*proto.Setting{
// 			{
// 				Name: "settingName",
// 				Resource: &proto.Resource{
// 					Id:   settingsStub[0].Id,
// 					Type: proto.Resource_TYPE_SETTING,
// 				},
// 				Value: &proto.Setting_PermissionValue{
// 					&proto.Permission{
// 						Operation:  proto.PermissionSetting_OPERATION_UPDATE,
// 						Constraint: proto.PermissionSetting_CONSTRAINT_OWN,
// 					},
// 				},
// 			},
// 		},
// 		Resource: &proto.Resource{
// 			Type: proto.Resource_TYPE_SYSTEM,
// 		},
// 	},
// })
// assert.NoError(t, err)

// _, err = rc.AssignRoleToUser(context.Background(), &proto.AssignRoleToUserRequest{
// 	AccountUuid: "4c510ada-c86b-4815-8820-42cdf82c3d51",
// 	RoleId:      res.Bundle.Id,
// })
// assert.NoError(t, err)

// 	time.Sleep(200 * time.Millisecond)
// 	listRequest := proto.ListSettingsBundlesRequest{AccountUuid: "4c510ada-c86b-4815-8820-42cdf82c3d51"}

// 	response, err := cl.ListSettingsBundles(context.Background(), &listRequest)
// 	assert.NoError(t, err)
// 	assert.Equal(t, 1, len(response.Bundles))
// 	assert.Equal(t, response.Bundles[0].Name, "bundle1")
//

func TestListRolesAfterSavingBundle(t *testing.T) {
	type expectedBundle struct {
		displayName string
		name        string
	}

	tests := []struct {
		name            string
		bundles         []*proto.Bundle
		expectedBundles []expectedBundle
	}{
		{"don't add bundle",
			[]*proto.Bundle{},
			[]expectedBundle{
				{displayName: "Guest", name: "guest"},
				{displayName: "Admin", name: "admin"},
				{displayName: "User", name: "user"},
			},
		},
		{name: "one bundle",
			bundles: []*proto.Bundle{{
				Type:        proto.Bundle_TYPE_ROLE,
				DisplayName: "test role - update",
				Name:        "TEST_ROLE",
				Extension:   "ocis-settings",
				Settings: []*proto.Setting{
					{
						Name: "settingName",
						Resource: &proto.Resource{
							Id:   settingsStub[0].Id,
							Type: proto.Resource_TYPE_SETTING,
						},
						Value: &proto.Setting_PermissionValue{
							PermissionValue: &proto.Permission{
								Operation:  proto.Permission_OPERATION_UPDATE,
								Constraint: proto.Permission_CONSTRAINT_OWN,
							},
						},
					},
				},
				Resource: &proto.Resource{
					Type: proto.Resource_TYPE_SYSTEM,
				},
			}},
			expectedBundles: []expectedBundle{
				{displayName: "test role - update", name: "TEST_ROLE"},
				{displayName: "Guest", name: "guest"},
				{displayName: "Admin", name: "admin"},
				{displayName: "User", name: "user"},
			},
		},
		{name: "two added bundles",
			bundles: []*proto.Bundle{{
				Type:        proto.Bundle_TYPE_ROLE,
				DisplayName: "test role - update",
				Name:        "TEST_ROLE",
				Extension:   "ocis-settings",
				Settings: []*proto.Setting{
					{
						Name: "settingName",
						Resource: &proto.Resource{
							Id:   settingsStub[0].Id,
							Type: proto.Resource_TYPE_SETTING,
						},
						Value: &proto.Setting_PermissionValue{
							PermissionValue: &proto.Permission{
								Operation:  proto.Permission_OPERATION_UPDATE,
								Constraint: proto.Permission_CONSTRAINT_OWN,
							},
						},
					},
				},
				Resource: &proto.Resource{
					Type: proto.Resource_TYPE_SYSTEM,
				},
			},
				{
					Type:        proto.Bundle_TYPE_ROLE,
					DisplayName: "an other role",
					Name:        "AnOtherROLE",
					Extension:   "ocis-settings",
					Settings: []*proto.Setting{
						{
							Name: "settingName",
							Resource: &proto.Resource{
								Id:   settingsStub[0].Id,
								Type: proto.Resource_TYPE_SETTING,
							},
							Value: &proto.Setting_PermissionValue{
								PermissionValue: &proto.Permission{
									Operation:  proto.Permission_OPERATION_UPDATE,
									Constraint: proto.Permission_CONSTRAINT_OWN,
								},
							},
						},
					},
					Resource: &proto.Resource{
						Type: proto.Resource_TYPE_SYSTEM,
					},
				}},
			expectedBundles: []expectedBundle{
				{displayName: "test role - update", name: "TEST_ROLE"},
				{displayName: "an other role", name: "AnOtherROLE"},
				{displayName: "Guest", name: "guest"},
				{displayName: "Admin", name: "admin"},
				{displayName: "User", name: "user"},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			teardown := setup()
			defer teardown()

			ctx := metadata.Set(context.Background(), middleware.AccountID, testAccountID)
			ctx = metadata.Set(ctx, middleware.RoleIDs, getRoleIDAsJSON(svc.BundleUUIDRoleAdmin))

			for _, bundle := range tt.bundles {
				_, err := bundleService.SaveBundle(ctx, &proto.SaveBundleRequest{
					Bundle: bundle,
				})
				assert.NoError(t, err)
			}
			rolesRes, err := roleService.ListRoles(ctx, &proto.ListBundlesRequest{})
			assert.NoError(t, err)

			for _, bundle := range rolesRes.Bundles {
				assert.Contains(t, tt.expectedBundles, expectedBundle{
					displayName: bundle.DisplayName,
					name:        bundle.Name,
				})
			}
		})
	}
}

func TestListFilteredBundle(t *testing.T) {
	type expectedBundle struct {
		displayName string
		name        string
	}

	type permission struct {
		permission proto.Permission_Operation
		roleUUID   string
	}

	type bundleForTest struct {
		bundle     *proto.Bundle
		permission permission
	}

	tests := []struct {
		name            string
		bundles         []bundleForTest
		expectedBundles []expectedBundle
	}{
		{
			name: "multiple bundles, all have READ(WRITE) permission",
			bundles: []bundleForTest{
				{
					bundle: &proto.Bundle{
						Name:        "test",
						Id:          "b1b8c9d0-fb3c-4e12-b868-5a8508218d2e",
						DisplayName: "bundleDisplayName",
						Extension:   "testExtension",
						Type:        proto.Bundle_TYPE_DEFAULT,
						Settings:    complexSettingsStub,
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_SYSTEM,
						},
					},
					permission: permission{
						permission: proto.Permission_OPERATION_READWRITE,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
				{
					bundle: &proto.Bundle{
						Name:        "one-more",
						Id:          "3b9f230a-fc9e-4605-89ee-a21e24728c64",
						DisplayName: "an other bundle",
						Extension:   "testExtension",
						Type:        proto.Bundle_TYPE_DEFAULT,
						Settings:    complexSettingsStub,
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_SYSTEM,
						},
					},
					permission: permission{
						permission: proto.Permission_OPERATION_READ,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
			},
			expectedBundles: []expectedBundle{
				{displayName: "bundleDisplayName", name: "test"},
				{displayName: "an other bundle", name: "one-more"},
			},
		},
		{
			name: "multiple bundles, only one with READ permission",
			bundles: []bundleForTest{
				{
					bundle: &proto.Bundle{
						Name:        "Permission_OPERATION_WRITE",
						Id:          "12fe2b67-4a08-4f17-9cb6-924da943da0e",
						DisplayName: "Permission_OPERATION_WRITE",
						Extension:   "testExtension",
						Type:        proto.Bundle_TYPE_DEFAULT,
						Settings:    complexSettingsStub,
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_SYSTEM,
						},
					},
					permission: permission{
						permission: proto.Permission_OPERATION_WRITE,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
				{
					bundle: &proto.Bundle{
						Name:        "Permission_OPERATION_DELETE",
						Id:          "1a0b65b0-fdbf-4738-b41e-41d36a01376e",
						DisplayName: "Permission_OPERATION_DELETE",
						Extension:   "testExtension",
						Type:        proto.Bundle_TYPE_DEFAULT,
						Settings:    complexSettingsStub,
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_SYSTEM,
						},
					},
					permission: permission{
						permission: proto.Permission_OPERATION_DELETE,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
				{
					bundle: &proto.Bundle{
						Name:        "Permission_OPERATION_UPDATE",
						Id:          "511fe78e-89c9-4237-a01e-6af5457a135e",
						DisplayName: "Permission_OPERATION_UPDATE",
						Extension:   "testExtension",
						Type:        proto.Bundle_TYPE_DEFAULT,
						Settings:    complexSettingsStub,
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_SYSTEM,
						},
					},
					permission: permission{
						permission: proto.Permission_OPERATION_UPDATE,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
				{
					bundle: &proto.Bundle{
						Name:        "Permission_OPERATION_CREATE",
						Id:          "aa42fb12-57aa-40c0-b458-3a91f398deba",
						DisplayName: "Permission_OPERATION_CREATE",
						Extension:   "testExtension",
						Type:        proto.Bundle_TYPE_DEFAULT,
						Settings:    complexSettingsStub,
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_SYSTEM,
						},
					},
					permission: permission{
						permission: proto.Permission_OPERATION_CREATE,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
				{
					bundle: &proto.Bundle{
						Name:        "Permission_OPERATION_UNKNOWN",
						Id:          "eabb2a18-09e2-4b06-aa62-987e8dc5e908",
						DisplayName: "Permission_OPERATION_UNKNOWN",
						Extension:   "testExtension",
						Type:        proto.Bundle_TYPE_DEFAULT,
						Settings:    complexSettingsStub,
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_SYSTEM,
						},
					},
					permission: permission{
						permission: proto.Permission_OPERATION_UNKNOWN,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
				{
					bundle: &proto.Bundle{
						Name:        "Permission_OPERATION_READ",
						Id:          "3b9f230a-fc9e-4605-89ee-a21e24728c64",
						DisplayName: "Permission_OPERATION_READ",
						Extension:   "testExtension",
						Type:        proto.Bundle_TYPE_DEFAULT,
						Settings:    complexSettingsStub,
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_SYSTEM,
						},
					},
					permission: permission{
						permission: proto.Permission_OPERATION_READ,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
			},
			expectedBundles: []expectedBundle{
				{displayName: "Permission_OPERATION_READ", name: "Permission_OPERATION_READ"},
			},
		},
		{
			name: "multiple bundles, all have READ permission, but only one matching role",
			bundles: []bundleForTest{
				{
					bundle: &proto.Bundle{
						Name:        "matching-role",
						Id:          "b1b8c9d0-fb3c-4e12-b868-5a8508218d2e",
						DisplayName: "bundleDisplayName",
						Extension:   "testExtension",
						Type:        proto.Bundle_TYPE_DEFAULT,
						Settings:    complexSettingsStub,
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_SYSTEM,
						},
					},
					permission: permission{
						permission: proto.Permission_OPERATION_READWRITE,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
				{
					bundle: &proto.Bundle{
						Name:        "NOT-matching-role",
						Id:          "3b9f230a-fc9e-4605-89ee-a21e24728c64",
						DisplayName: "an other bundle",
						Extension:   "testExtension",
						Type:        proto.Bundle_TYPE_DEFAULT,
						Settings:    complexSettingsStub,
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_SYSTEM,
						},
					},
					permission: permission{
						permission: proto.Permission_OPERATION_READ,
						roleUUID:   svc.BundleUUIDRoleGuest,
					},
				},
				{
					bundle: &proto.Bundle{
						Name:        "NOT-matching-role2",
						Id:          "714a5917-627c-40ac-8dc7-5fdac013e4b7",
						DisplayName: "an other bundle",
						Extension:   "testExtension",
						Type:        proto.Bundle_TYPE_DEFAULT,
						Settings:    complexSettingsStub,
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_SYSTEM,
						},
					},
					permission: permission{
						permission: proto.Permission_OPERATION_READ,
						roleUUID:   svc.BundleUUIDRoleUser,
					},
				},
			},
			expectedBundles: []expectedBundle{
				{displayName: "bundleDisplayName", name: "matching-role"},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			teardown := setup()
			defer teardown()

			ctx := metadata.Set(context.Background(), middleware.AccountID, testAccountID)
			ctx = metadata.Set(ctx, middleware.RoleIDs, getRoleIDAsJSON(svc.BundleUUIDRoleAdmin))

			for _, testBundle := range tt.bundles {
				_, err := bundleService.SaveBundle(ctx, &proto.SaveBundleRequest{
					Bundle: testBundle.bundle,
				})
				assert.NoError(t, err)

				setPermissionOnBundleOrSetting(
					ctx, t, testBundle.bundle.Id, proto.Resource_TYPE_BUNDLE,
					testBundle.permission.permission, testBundle.permission.roleUUID,
				)
			}

			listRes, err := bundleService.ListBundles(ctx, &proto.ListBundlesRequest{})
			assert.NoError(t, err)

			// we don't want to deep-assert the values returned only add checks on name and displayName
			// this will suffice.
			listResAsExpectedBundle := make([]expectedBundle, 0)
			for i := range listRes.Bundles {
				listResAsExpectedBundle = append(listResAsExpectedBundle, expectedBundle{
					displayName: listRes.Bundles[i].DisplayName,
					name:        listRes.Bundles[i].Name,
				})
			}

			for _, bundle := range tt.expectedBundles {
				assert.Contains(t, listResAsExpectedBundle, bundle)
			}
		})
	}
}

func TestListGetBundleSettingMixedPermission(t *testing.T) {
	type expectedSetting struct {
		displayName string
		name        string
	}

	type permission struct {
		permission proto.Permission_Operation
		roleUUID   string
	}

	type settingsForTest struct {
		setting    *proto.Setting
		permission permission
	}

	tests := []struct {
		name             string
		settings         []settingsForTest
		expectedSettings []expectedSetting
	}{
		{
			name: "all settings have R/RW permissions",
			settings: []settingsForTest{
				{
					setting: &proto.Setting{
						Id:          "b86fdb0a-801f-4749-ab84-5c99e90dbd6d",
						DisplayName: "RW setting",
						Name:        "RW-setting",
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_USER,
						},
						Value: &proto.Setting_IntValue{
							IntValue: &proto.Int{
								Default: 42,
							},
						},
						Description: "RW setting",
					},
					permission: permission{
						permission: proto.Permission_OPERATION_READWRITE,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
				{
					setting: &proto.Setting{
						Id:          "cb1bbe58-27e7-461b-91b1-a9c85c488789",
						DisplayName: "RO setting",
						Name:        "RO-setting",
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_USER,
						},
						Value: &proto.Setting_IntValue{
							IntValue: &proto.Int{
								Default: 42,
							},
						},
						Description: "RO setting",
					},
					permission: permission{
						permission: proto.Permission_OPERATION_READWRITE,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
			},
			expectedSettings: []expectedSetting{
				{displayName: "RW setting", name: "RW-setting"},
				{displayName: "RO setting", name: "RO-setting"},
			},
		},
		{
			name: "all settings have R/RW permissions but only one the matching user",
			settings: []settingsForTest{
				{
					setting: &proto.Setting{
						Id:          "b86fdb0a-801f-4749-ab84-5c99e90dbd6d",
						DisplayName: "matching user",
						Name:        "matching-user",
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_USER,
						},
						Value: &proto.Setting_IntValue{
							IntValue: &proto.Int{
								Default: 42,
							},
						},
						Description: "matching user",
					},
					permission: permission{
						permission: proto.Permission_OPERATION_READWRITE,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
				{
					setting: &proto.Setting{
						Id:          "cb1bbe58-27e7-461b-91b1-a9c85c488789",
						DisplayName: "NOT matching user",
						Name:        "NOT-matching-user",
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_USER,
						},
						Value: &proto.Setting_IntValue{
							IntValue: &proto.Int{
								Default: 42,
							},
						},
						Description: "NOT matching user",
					},
					permission: permission{
						permission: proto.Permission_OPERATION_READWRITE,
						roleUUID:   svc.BundleUUIDRoleGuest,
					},
				},
			},
			expectedSettings: []expectedSetting{
				{displayName: "matching user", name: "matching-user"},
			},
		},
		{
			name: "only one settings has READ permissions",
			settings: []settingsForTest{
				{
					setting: &proto.Setting{
						Id:          "b86fdb0a-801f-4749-ab84-5c99e90dbd6d",
						DisplayName: "WRITE setting",
						Name:        "WRITE-setting",
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_USER,
						},
						Value: &proto.Setting_IntValue{
							IntValue: &proto.Int{
								Default: 42,
							},
						},
						Description: "WRITE setting",
					},
					permission: permission{
						permission: proto.Permission_OPERATION_WRITE,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
				{
					setting: &proto.Setting{
						Id:          "6163c6bf-79f2-43f7-b0ba-1493534bfc10",
						DisplayName: "UNKNOWN setting",
						Name:        "UNKNOWN-setting",
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_USER,
						},
						Value: &proto.Setting_IntValue{
							IntValue: &proto.Int{
								Default: 42,
							},
						},
						Description: "UNKNOWN setting",
					},
					permission: permission{
						permission: proto.Permission_OPERATION_UNKNOWN,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
				{
					setting: &proto.Setting{
						Id:          "79eda727-9fa1-459f-aaff-f73ed5693419",
						DisplayName: "CREATE setting",
						Name:        "CREATE-setting",
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_USER,
						},
						Value: &proto.Setting_IntValue{
							IntValue: &proto.Int{
								Default: 42,
							},
						},
						Description: "CREATE setting",
					},
					permission: permission{
						permission: proto.Permission_OPERATION_CREATE,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
				{
					setting: &proto.Setting{
						Id:          "2be7ca51-89fb-4968-b9d2-0ac43197adff",
						DisplayName: "UPDATE setting",
						Name:        "UPDATE-setting",
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_USER,
						},
						Value: &proto.Setting_IntValue{
							IntValue: &proto.Int{
								Default: 42,
							},
						},
						Description: "UPDATE setting",
					},
					permission: permission{
						permission: proto.Permission_OPERATION_UPDATE,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
				{
					setting: &proto.Setting{
						Id:          "f1a0005e-e570-4bd8-a18c-b4afaaa8d7d9",
						DisplayName: "DELETE setting",
						Name:        "DELETE-setting",
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_USER,
						},
						Value: &proto.Setting_IntValue{
							IntValue: &proto.Int{
								Default: 42,
							},
						},
						Description: "DELETE setting",
					},
					permission: permission{
						permission: proto.Permission_OPERATION_DELETE,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
				{
					setting: &proto.Setting{
						Id:          "cb1bbe58-27e7-461b-91b1-a9c85c488789",
						DisplayName: "RO setting",
						Name:        "RO-setting",
						Resource: &proto.Resource{
							Type: proto.Resource_TYPE_USER,
						},
						Value: &proto.Setting_IntValue{
							IntValue: &proto.Int{
								Default: 42,
							},
						},
						Description: "RO setting",
					},
					permission: permission{
						permission: proto.Permission_OPERATION_READWRITE,
						roleUUID:   svc.BundleUUIDRoleAdmin,
					},
				},
			},
			expectedSettings: []expectedSetting{
				{displayName: "RO setting", name: "RO-setting"},
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			teardown := setup()
			defer teardown()

			ctx := metadata.Set(context.Background(), middleware.AccountID, testAccountID)
			ctx = metadata.Set(ctx, middleware.RoleIDs, getRoleIDAsJSON(svc.BundleUUIDRoleAdmin))

			// create bundle with the defined settings
			var settings []*proto.Setting

			for _, testSetting := range tt.settings {
				settings = append(settings, testSetting.setting)
			}

			bundle := proto.Bundle{
				Name:        "test",
				Id:          "b1b8c9d0-fb3c-4e12-b868-5a8508218d2e",
				DisplayName: "bundleDisplayName",
				Extension:   "testExtension",
				Type:        proto.Bundle_TYPE_DEFAULT,
				Settings:    settings,
				Resource: &proto.Resource{
					Type: proto.Resource_TYPE_SYSTEM,
				},
			}

			_, err := bundleService.SaveBundle(ctx, &proto.SaveBundleRequest{
				Bundle: &bundle,
			})
			assert.NoError(t, err)

			// set permissions for each setting
			for _, testSetting := range tt.settings {
				setPermissionOnBundleOrSetting(
					ctx, t, testSetting.setting.Id, proto.Resource_TYPE_SETTING,
					testSetting.permission.permission, testSetting.permission.roleUUID,
				)
			}

			listRes, err := bundleService.ListBundles(ctx, &proto.ListBundlesRequest{})
			assert.NoError(t, err)

			listedSettings := make([]expectedSetting, 0)
			for i := range listRes.Bundles {
				for _, setting := range listRes.Bundles[i].Settings {
					listedSettings = append(listedSettings, expectedSetting{
						displayName: setting.DisplayName,
						name:        setting.Name,
					})
				}
			}

			for i := range tt.expectedSettings {
				assert.Contains(t, listedSettings, tt.expectedSettings[i])
			}

			getRes, err := bundleService.GetBundle(ctx, &proto.GetBundleRequest{BundleId: bundle.Id})
			assert.NoError(t, err)

			for _, setting := range getRes.Bundle.Settings {
				assert.Contains(t, tt.expectedSettings, expectedSetting{
					displayName: setting.DisplayName,
					name:        setting.Name,
				})
			}
		})
	}
}

func setFullReadWriteOnBundleForAdmin(ctx context.Context, t *testing.T, bundleID string) {
	setPermissionOnBundleOrSetting(
		ctx, t, bundleID, proto.Resource_TYPE_BUNDLE, proto.Permission_OPERATION_READWRITE, svc.BundleUUIDRoleAdmin,
	)
}

func setPermissionOnBundleOrSetting(
	ctx context.Context,
	t *testing.T,
	bundleID string,
	resourceType proto.Resource_Type,
	permission proto.Permission_Operation,
	roleUUID string,
) {
	permissionRequest := proto.AddSettingToBundleRequest{
		BundleId: roleUUID,
		Setting: &proto.Setting{
			Name: "test-bundle-permission-readwrite",
			Resource: &proto.Resource{
				Type: resourceType,
				Id:   bundleID,
			},
			Value: &proto.Setting_PermissionValue{
				PermissionValue: &proto.Permission{
					Operation:  permission,
					Constraint: proto.Permission_CONSTRAINT_OWN,
				},
			},
		},
	}
	addPermissionResponse, err := bundleService.AddSettingToBundle(ctx, &permissionRequest)
	assert.NoError(t, err)
	if err == nil {
		assert.NotEmpty(t, addPermissionResponse.Setting)
	}
}

func getRoleIDAsJSON(roleID string) string {
	roleIDsJSON, _ := json.Marshal([]string{roleID})
	return string(roleIDsJSON)
}
