package loaddata import ( "cmp" "context" "encoding/json" "io" "net/http" "net/url" "os" "path/filepath" "slices" "strings" "testing" "time" "github.com/99designs/gqlgen/graphql" graphqlTypes "github.com/shurcooL/graphql" "github.com/stretchr/testify/require" "edge-infra.dev/pkg/edge/api/graph/model" "edge-infra.dev/pkg/edge/api/utils" "edge-infra.dev/pkg/edge/edgecli" "edge-infra.dev/pkg/edge/edgecli/flagutil" ) var defaultConfig = ` { "ROLE_MAPPING": { "EDGE_BANNER_ADMIN": [ "ea-read", "ea-banner-admin" ] }, "RULES": { "ea-write": [ "ls", "cat" ], "ea-banner-admin": [ "kubectl" ] } } ` // testing helper type type helper interface { Helper() } func ErrorEqualMsg(msg string) require.ErrorAssertionFunc { return func(tt require.TestingT, err error, _ ...interface{}) { if help, ok := tt.(helper); ok { help.Helper() } require.EqualError(tt, err, msg) } } // Tests that an error that to consists of several joined-together error messages // is what we expect, with no respect to ordering. func ErrorEqualMsgElements(msg string) require.ErrorAssertionFunc { return func(tt require.TestingT, err error, _ ...interface{}) { if help, ok := tt.(helper); ok { help.Helper() } expected := strings.Split(msg, "\n") actual := strings.Split(err.Error(), "\n") for _, errMsg := range expected { require.Contains(tt, actual, errMsg) } } } func TestValidate(t *testing.T) { t.Parallel() tests := map[string]struct { cfg config expErr require.ErrorAssertionFunc }{ "Valid Full Config": { cfg: config{ RoleMappings: map[string][]string{ "role1": { "priv1", "priv2", }, "role2": { "priv3", }, }, Rules: map[string][]string{ "priv1": { "command1", "command2", }, "priv2": { "command3", }, }, }, expErr: require.NoError, }, "Valid Half Config": { cfg: config{ RoleMappings: map[string][]string{ "role1": { "priv1", }, }, }, expErr: require.NoError, }, "Invalid Uninitialized Maps": { cfg: config{ RoleMappings: map[string][]string{}, Rules: map[string][]string{}, }, expErr: ErrorEqualMsg("empty config"), }, "Invalid No Config": { cfg: config{}, expErr: ErrorEqualMsg("empty config"), }, "Invalid Full Config": { cfg: config{ RoleMappings: map[string][]string{ "role1": { // Privileges must be non-nil "", }, // Every role must have at least one privilege "role2": {}, }, Rules: map[string][]string{ "priv1": { // Commands must be non-nil "", }, // Every privilege must have at least one command "priv2": {}, }, }, expErr: ErrorEqualMsgElements("role \"role1\" has empty privilege\nrole \"role2\" has no privileges\nprivilege \"priv1\" has empty command\nprivilege \"priv2\" has no commands"), }, } for name, tc := range tests { tc := tc t.Run(name, func(t *testing.T) { t.Parallel() tc.expErr(t, tc.cfg.Validate()) }) } } func TestLoadConfig(t *testing.T) { t.Parallel() tests := map[string]struct { testData string wantConfig config requireError require.ErrorAssertionFunc }{ "ValidConfig": { testData: `{ "ROLE_MAPPING": { "EDGE_BANNER_ADMIN": [ "ea-read", "ea-banner-admin" ] }, "RULES": { "ea-write": [ "ls", "cat" ], "ea-banner-admin": [ "kubectl" ] } }`, wantConfig: config{ RoleMappings: map[string][]string{ "EDGE_BANNER_ADMIN": { "ea-read", "ea-banner-admin", }, }, Rules: map[string][]string{ "ea-write": { "ls", "cat", }, "ea-banner-admin": { "kubectl", }, }, }, requireError: require.NoError, }, "Duplicate Keys": { // Note if duplicate keys exist in the config.json, one set of keys // is lost, it is not additive testData: `{ "ROLE_MAPPING": { "EDGE_BANNER_ADMIN": [ "ea-admin" ], "EDGE_ORG_ADMIN": [ "ea-admin" ], "EDGE_BANNER_ADMIN": [ "ea-write" ] } }`, wantConfig: config{ RoleMappings: map[string][]string{ "EDGE_BANNER_ADMIN": { "ea-write", }, "EDGE_ORG_ADMIN": { "ea-admin", }, }, }, requireError: require.NoError, }, "InvalidConfig": { testData: `invalid json`, wantConfig: config{}, requireError: ErrorEqualMsg("invalid character 'i' looking for beginning of value"), }, "Invalid After Valid JSON": { testData: `{ "ROLE_MAPPING": { "EDGE_BANNER_ADMIN": [ "ea-banner-admin" ] }, "RULES": { "ea-banner-admin": [ "kubectl" ] } }INVALIDINPUTOVERHERE`, wantConfig: config{}, requireError: ErrorEqualMsg("error multiple objects in config file"), }, "Invalid Multiple Valid JSON Objects": { testData: `{ "ROLE_MAPPING": { "EDGE_BANNER_ADMIN": [ "ea-banner-admin" ] } }, { "RULES": { "ea-banner-admin": [ "kubectl" ] } }`, wantConfig: config{}, requireError: ErrorEqualMsg("error multiple objects in config file"), }, "Unknown Fields": { testData: `{ "ROLE_MAPPING": { "EDGE_BANNER_ADMIN": [ "ea-read" ] }, "RULES": { "ea-write": [ "ls" ] }, "somethingelse": { "somethingelse": [ "somethingelse" ] } }`, requireError: ErrorEqualMsg("json: unknown field \"somethingelse\""), }, "EmptyConfig": { testData: `{}`, requireError: ErrorEqualMsg("error invalid config: empty config"), }, "Empty Fields": { testData: `{ "ROLE_MAPPING": {}, "RULES": {} }`, requireError: ErrorEqualMsg("error invalid config: empty config"), }, } for name, tt := range tests { tt := tt t.Run(name, func(t *testing.T) { t.Parallel() // Create a temporary directory for testing tempDir := t.TempDir() // Create the file path filePath := filepath.Join(tempDir, "config.json") // Write test data to the temporary file err := os.WriteFile(filePath, []byte(tt.testData), 0644) if err != nil { t.Fatal(err) } // Call the loadConfig function conf, err := loadConfig(filePath) // Verify the error tt.requireError(t, err) // Verify the loaded configuration require.EqualValues(t, tt.wantConfig, conf) }) } } func TestVariables(t *testing.T) { t.Parallel() tests := map[string]struct { conf config expVariables map[string]interface{} }{ "No Config": { conf: config{ Rules: nil, RoleMappings: nil, }, expVariables: map[string]interface{}{ // Every value must be initialised (non-nil) even if they are // not being applied, otherwise the graphql schema is invalid "commands": []model.OperatorInterventionCommandInput{}, "privileges": []model.OperatorInterventionPrivilegeInput{}, "rules": []model.UpdateOperatorInterventionRuleInput{}, "roleMappings": []*model.UpdateOperatorInterventionRoleMappingInput{}, // Every type should be skipped "skipCommands": graphqlTypes.Boolean(true), "skipPrivileges": graphqlTypes.Boolean(true), "skipRules": graphqlTypes.Boolean(true), "skipRoleMappings": graphqlTypes.Boolean(true), }, }, "Only Role Mappings": { conf: config{ Rules: nil, RoleMappings: map[string][]string{ "EDGE_ORG_ADMIN": { "ea-read", "ea-write", }, }, }, expVariables: map[string]interface{}{ // Every value must be initialised (non-nil) even if they are // not being applied, otherwise the graphql schema is invalid // Commands and rules should not be added "commands": []model.OperatorInterventionCommandInput{}, "rules": []model.UpdateOperatorInterventionRuleInput{}, // All privileges referenced in roles must be added "privileges": []model.OperatorInterventionPrivilegeInput{ {Name: "ea-read"}, {Name: "ea-write"}, }, "roleMappings": []*model.UpdateOperatorInterventionRoleMappingInput{ { Role: "EDGE_ORG_ADMIN", Privileges: []*model.OperatorInterventionPrivilegeInput{ {Name: "ea-read"}, {Name: "ea-write"}, }, }, }, // commands and rules must be skipped as there is no data to add "skipCommands": graphqlTypes.Boolean(true), "skipRules": graphqlTypes.Boolean(true), "skipPrivileges": graphqlTypes.Boolean(false), "skipRoleMappings": graphqlTypes.Boolean(false), }, }, "Only Rules": { conf: config{ Rules: map[string][]string{ "ea-admin": {"ls", "systemctl"}, "ea-read": {"ls", "cat"}, }, RoleMappings: nil, }, expVariables: map[string]interface{}{ // Every value must be initialised (non-nil) even if they are // not being applied, otherwise the graphql schema is invalid // Only role mappings should not be added "roleMappings": []*model.UpdateOperatorInterventionRoleMappingInput{}, // All privileges and commands referenced in rules must be added "privileges": []model.OperatorInterventionPrivilegeInput{ {Name: "ea-admin"}, {Name: "ea-read"}, }, "commands": []model.OperatorInterventionCommandInput{ // Note deduplication occurs {Name: "cat"}, {Name: "ls"}, {Name: "systemctl"}, }, "rules": []model.UpdateOperatorInterventionRuleInput{ { Privilege: &model.OperatorInterventionPrivilegeInput{Name: "ea-admin"}, Commands: []*model.OperatorInterventionCommandInput{{Name: "ls"}, {Name: "systemctl"}}, }, { Privilege: &model.OperatorInterventionPrivilegeInput{Name: "ea-read"}, Commands: []*model.OperatorInterventionCommandInput{{Name: "ls"}, {Name: "cat"}}, }, }, // Only role mappings are skipped "skipRoleMappings": graphqlTypes.Boolean(true), "skipCommands": graphqlTypes.Boolean(false), "skipPrivileges": graphqlTypes.Boolean(false), "skipRules": graphqlTypes.Boolean(false), }, }, "Both Rules and Mappings": { conf: config{ Rules: map[string][]string{ "ea-admin": {"ls", "systemctl"}, "ea-read": {"ls", "cat"}, }, RoleMappings: map[string][]string{ "EDGE_ORG_ADMIN": { "ea-read", "ea-write", }, }, }, expVariables: map[string]interface{}{ "commands": []model.OperatorInterventionCommandInput{ // Note no deduplication occurs {Name: "cat"}, {Name: "ls"}, {Name: "systemctl"}, }, // All privileges referenced in either roles or rules must be // included "privileges": []model.OperatorInterventionPrivilegeInput{ // Note deduplication occurs {Name: "ea-admin"}, {Name: "ea-read"}, {Name: "ea-write"}, }, "rules": []model.UpdateOperatorInterventionRuleInput{ { Privilege: &model.OperatorInterventionPrivilegeInput{Name: "ea-admin"}, Commands: []*model.OperatorInterventionCommandInput{{Name: "ls"}, {Name: "systemctl"}}, }, { Privilege: &model.OperatorInterventionPrivilegeInput{Name: "ea-read"}, Commands: []*model.OperatorInterventionCommandInput{{Name: "ls"}, {Name: "cat"}}, }, }, "roleMappings": []*model.UpdateOperatorInterventionRoleMappingInput{ { Role: "EDGE_ORG_ADMIN", Privileges: []*model.OperatorInterventionPrivilegeInput{{Name: "ea-read"}, {Name: "ea-write"}}, }, }, // Every type should be added "skipCommands": graphqlTypes.Boolean(false), "skipPrivileges": graphqlTypes.Boolean(false), "skipRules": graphqlTypes.Boolean(false), "skipRoleMappings": graphqlTypes.Boolean(false), }, }, "Duplicate Rules": { // Notice when the commands within a single rule is duplicated, the // commands mutation doesn't include duplicate entries, but the // rules mutation does conf: config{ Rules: map[string][]string{ "ea-admin": {"ls", "ls"}, }, RoleMappings: nil, }, expVariables: map[string]interface{}{ // Every value must be initialised (non-nil) even if they are // not being applied, otherwise the graphql schema is invalid // Only role mappings should not be added "roleMappings": []*model.UpdateOperatorInterventionRoleMappingInput{}, // All privileges and commands referenced in rules must be added "privileges": []model.OperatorInterventionPrivilegeInput{ {Name: "ea-admin"}, }, "commands": []model.OperatorInterventionCommandInput{ // Note deduplication occurs here {Name: "ls"}, }, "rules": []model.UpdateOperatorInterventionRuleInput{ { Privilege: &model.OperatorInterventionPrivilegeInput{Name: "ea-admin"}, // Note deduplication does not occur here Commands: []*model.OperatorInterventionCommandInput{{Name: "ls"}, {Name: "ls"}}, }, }, // Only role mappings are skipped "skipRoleMappings": graphqlTypes.Boolean(true), "skipCommands": graphqlTypes.Boolean(false), "skipPrivileges": graphqlTypes.Boolean(false), "skipRules": graphqlTypes.Boolean(false), }, }, "No privileges in role mapping": { conf: config{ RoleMappings: map[string][]string{ "EDGE_ORG_ADMIN": {}, }, }, expVariables: map[string]interface{}{ "commands": []model.OperatorInterventionCommandInput{}, "rules": []model.UpdateOperatorInterventionRuleInput{}, "privileges": []model.OperatorInterventionPrivilegeInput{}, "roleMappings": []*model.UpdateOperatorInterventionRoleMappingInput{ { Role: "EDGE_ORG_ADMIN", Privileges: []*model.OperatorInterventionPrivilegeInput{}, }, }, "skipCommands": graphqlTypes.Boolean(true), "skipRules": graphqlTypes.Boolean(true), "skipPrivileges": graphqlTypes.Boolean(true), "skipRoleMappings": graphqlTypes.Boolean(false), }, }, "No commands in rule": { conf: config{ Rules: map[string][]string{ "ea-admin": {}, }, }, expVariables: map[string]interface{}{ "roleMappings": []*model.UpdateOperatorInterventionRoleMappingInput{}, "commands": []model.OperatorInterventionCommandInput{}, "privileges": []model.OperatorInterventionPrivilegeInput{ {Name: "ea-admin"}, }, "rules": []model.UpdateOperatorInterventionRuleInput{ { Privilege: &model.OperatorInterventionPrivilegeInput{Name: "ea-admin"}, Commands: []*model.OperatorInterventionCommandInput{}, }, }, "skipRoleMappings": graphqlTypes.Boolean(true), "skipCommands": graphqlTypes.Boolean(true), "skipRules": graphqlTypes.Boolean(false), "skipPrivileges": graphqlTypes.Boolean(false), }, }, } for name, tc := range tests { tc := tc t.Run(name, func(t *testing.T) { t.Parallel() vars := createVariables(tc.conf) // Sort the rules for deterministic test output slices.SortFunc(vars["rules"].([]model.UpdateOperatorInterventionRuleInput), func(a, b model.UpdateOperatorInterventionRuleInput) int { return cmp.Compare(a.Privilege.Name, b.Privilege.Name) }) require.Equal(t, tc.expVariables, vars) }) } } func TestMissingFlagFile(t *testing.T) { testConfig := edgecli.Config{} cmd := NewCmd(&testConfig) require.ErrorContains(t, cmd.Command().Exec(context.Background(), []string{}), "Flag 'file' is required") require.NoError(t, flagutil.SetFlag(cmd.Rags, flagutil.LoadData, "test-cluster-0")) } func TestLoadData(t *testing.T) { t.Parallel() server := utils.NewMockHTTPTestServer().AddAllowedContentType("application/json").DefaultNotFound() t.Cleanup(server.Server.Close) tests := map[string]struct { data map[string]interface{} // data api will return expError require.ErrorAssertionFunc }{ "Error Response": { data: map[string]interface{}{ "updateOperatorInterventionRoleMappings": model.UpdateOperatorInterventionRoleMappingResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownRole}, }, }, "createOperatorInterventionPrivileges": model.CreateOperatorInterventionPrivilegeResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownPrivilege}, }, }, }, expError: ErrorEqualMsg("error during mutation"), }, "No Error Response": { data: map[string]interface{}{ "updateOperatorInterventionRoleMappings": model.UpdateOperatorInterventionRoleMappingResponse{}, "createOperatorInterventionPrivileges": model.CreateOperatorInterventionPrivilegeResponse{}, }, expError: require.NoError, }, } for name, tc := range tests { name := name tc := tc t.Run(name, func(t *testing.T) { t.Parallel() // Setup dir := t.TempDir() filePath := filepath.Join(dir, "config.json") require.NoError(t, os.WriteFile( filePath, []byte(defaultConfig), 0644, )) server.Any(name, apiRequestCallback(tc.data), func(_ http.ResponseWriter, r *http.Request) bool { return strings.HasPrefix(r.URL.String(), "/"+url.PathEscape(name)) }) // Set a dummy token in a fake banner context so that we bypass the // ValidateConnectionFlags check which would otherwise do a login // mutation to the api server, something which is not supported by // the fake servert future := time.Now().Add(time.Hour * 24) testConfig := edgecli.Config{ CurrentBannerContext: "fakeBanner", BannerContexts: map[string]*edgecli.BannerContext{ "fakeBanner": { TokenTime: future.Format(time.RFC3339), Token: "fakeToken", Endpoint: server.Server.URL + "/" + url.PathEscape(name), }, }, } cmd := NewCmd(&testConfig) cmd.Command() // Required to initialise cmd.Rags require.NoError(t, flagutil.SetFlag(cmd.Rags, flagutil.LoadData, filePath)) // Test err := cmd.Command().Exec(context.Background(), []string{}) tc.expError(t, err) }) } } func apiRequestCallback(data interface{}) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { utils.WriteBadResponse(w, nil) return } mutationCorrect := assertMutationOrder(body) if !mutationCorrect { utils.WriteBadResponse(w, nil) return } data, err := wrapAsGraphqlResponse(data) if err != nil { utils.WriteBadResponse(w, nil) return } utils.WriteOkResponse(w, data) } } // assertMutationOrder asserts the mutations are ordered as we expect them to be. // commands and privileges must be added before rules and role mappings to // ensure that any new privs/commands have been committed to the DB before they // are referenced func assertMutationOrder(body []byte) bool { return all( assertOnce(string(body), "createOperatorInterventionCommands"), assertOnce(string(body), "createOperatorInterventionPrivileges"), assertOnce(string(body), "updateOperatorInterventionRules"), assertOnce(string(body), "updateOperatorInterventionRoleMappings"), assertInOrder(string(body), "createOperatorInterventionPrivileges", "updateOperatorInterventionRoleMappings"), assertInOrder(string(body), "createOperatorInterventionPrivileges", "updateOperatorInterventionRules"), assertInOrder(string(body), "createOperatorInterventionCommands", "updateOperatorInterventionRules"), ) } // all returns true when every parameter is true otherwise returns false func all(vals ...bool) bool { for _, val := range vals { if !val { return false } } return true } // assertOnce asserts the value only appears in body once func assertOnce(body string, value string) bool { return strings.Count(body, value) == 1 } // assertInOrder asserts that first appears before second within the data func assertInOrder(data string, first string, second string) bool { firstIdx := strings.Index(data, first) if firstIdx == -1 { return false } secondIdx := strings.Index(data, second) if secondIdx == -1 { return false } return second > first } func wrapAsGraphqlResponse(v interface{}) ([]byte, error) { res, err := json.Marshal(v) if err != nil { return nil, err } resp := graphql.Response{Data: res} return json.Marshal(resp) } func TestGenerateAllOutput(t *testing.T) { t.Parallel() var invalidCommand = "invalidCommand" var invalidRole = "invalidRole" var invalidPriv = "invalidPriv" tests := map[string]struct { variables map[string]interface{} mutation oiLoadDataMutation exp string errorAssertion require.ErrorAssertionFunc }{ "No Mutations, No Errors": { variables: map[string]interface{}{ "skipCommands": graphqlTypes.Boolean(true), "skipPrivileges": graphqlTypes.Boolean(true), "skipRules": graphqlTypes.Boolean(true), "skipRoleMappings": graphqlTypes.Boolean(true), }, mutation: oiLoadDataMutation{ CreateOperatorInterventionCommands: struct { model.CreateOperatorInterventionCommandResponse }{ CreateOperatorInterventionCommandResponse: model.CreateOperatorInterventionCommandResponse{ Errors: nil, }, }, CreateOperatorInterventionPrivileges: struct { model.CreateOperatorInterventionPrivilegeResponse }{ CreateOperatorInterventionPrivilegeResponse: model.CreateOperatorInterventionPrivilegeResponse{ Errors: nil, }, }, UpdateOperatorInterventionRules: struct { model.UpdateOperatorInterventionRuleResponse }{ UpdateOperatorInterventionRuleResponse: model.UpdateOperatorInterventionRuleResponse{ Errors: nil, }, }, UpdateOperatorInterventionRoleMappings: struct { model.UpdateOperatorInterventionRoleMappingResponse }{ UpdateOperatorInterventionRoleMappingResponse: model.UpdateOperatorInterventionRoleMappingResponse{ Errors: nil, }, }, }, exp: "", errorAssertion: require.NoError, }, "No Mutations, All Errors": { variables: map[string]interface{}{ "skipCommands": graphqlTypes.Boolean(true), "skipPrivileges": graphqlTypes.Boolean(true), "skipRules": graphqlTypes.Boolean(true), "skipRoleMappings": graphqlTypes.Boolean(true), }, mutation: oiLoadDataMutation{ CreateOperatorInterventionCommands: struct { model.CreateOperatorInterventionCommandResponse }{ CreateOperatorInterventionCommandResponse: model.CreateOperatorInterventionCommandResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownPrivilege}, }, }, }, CreateOperatorInterventionPrivileges: struct { model.CreateOperatorInterventionPrivilegeResponse }{ CreateOperatorInterventionPrivilegeResponse: model.CreateOperatorInterventionPrivilegeResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownPrivilege}, }, }, }, UpdateOperatorInterventionRules: struct { model.UpdateOperatorInterventionRuleResponse }{ UpdateOperatorInterventionRuleResponse: model.UpdateOperatorInterventionRuleResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownPrivilege}, }, }, }, UpdateOperatorInterventionRoleMappings: struct { model.UpdateOperatorInterventionRoleMappingResponse }{ UpdateOperatorInterventionRoleMappingResponse: model.UpdateOperatorInterventionRoleMappingResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownPrivilege}, }, }, }, }, exp: "", errorAssertion: require.NoError, }, "All Mutations, No Errors": { variables: map[string]interface{}{ "skipCommands": graphqlTypes.Boolean(false), "skipPrivileges": graphqlTypes.Boolean(false), "skipRules": graphqlTypes.Boolean(false), "skipRoleMappings": graphqlTypes.Boolean(false), }, mutation: oiLoadDataMutation{ CreateOperatorInterventionCommands: struct { model.CreateOperatorInterventionCommandResponse }{ CreateOperatorInterventionCommandResponse: model.CreateOperatorInterventionCommandResponse{ Errors: nil, }, }, CreateOperatorInterventionPrivileges: struct { model.CreateOperatorInterventionPrivilegeResponse }{ CreateOperatorInterventionPrivilegeResponse: model.CreateOperatorInterventionPrivilegeResponse{ Errors: nil, }, }, UpdateOperatorInterventionRules: struct { model.UpdateOperatorInterventionRuleResponse }{ UpdateOperatorInterventionRuleResponse: model.UpdateOperatorInterventionRuleResponse{ Errors: nil, }, }, UpdateOperatorInterventionRoleMappings: struct { model.UpdateOperatorInterventionRoleMappingResponse }{ UpdateOperatorInterventionRoleMappingResponse: model.UpdateOperatorInterventionRoleMappingResponse{ Errors: nil, }, }, }, exp: "\nOI Commands applied\n\n\nOI Privileges applied\n\n\nOI Rules applied\n\n\nOI Role Mappings applied\n", errorAssertion: require.NoError, }, "All Mutations, All Errors": { variables: map[string]interface{}{ "skipCommands": graphqlTypes.Boolean(false), "skipPrivileges": graphqlTypes.Boolean(false), "skipRules": graphqlTypes.Boolean(false), "skipRoleMappings": graphqlTypes.Boolean(false), }, mutation: oiLoadDataMutation{ CreateOperatorInterventionCommands: struct { model.CreateOperatorInterventionCommandResponse }{ CreateOperatorInterventionCommandResponse: model.CreateOperatorInterventionCommandResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownPrivilege}, }, }, }, CreateOperatorInterventionPrivileges: struct { model.CreateOperatorInterventionPrivilegeResponse }{ CreateOperatorInterventionPrivilegeResponse: model.CreateOperatorInterventionPrivilegeResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownPrivilege}, }, }, }, UpdateOperatorInterventionRules: struct { model.UpdateOperatorInterventionRuleResponse }{ UpdateOperatorInterventionRuleResponse: model.UpdateOperatorInterventionRuleResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownPrivilege, Privilege: &invalidPriv}, }, }, }, UpdateOperatorInterventionRoleMappings: struct { model.UpdateOperatorInterventionRoleMappingResponse }{ UpdateOperatorInterventionRoleMappingResponse: model.UpdateOperatorInterventionRoleMappingResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownRole, Role: &invalidRole}, }, }, }, }, exp: "\nErrors occurred when applying OI Commands: \n\tError: UNKNOWN_PRIVILEGE\n\nOI Commands not applied.\n\n\nErrors occurred when applying OI Privileges: \n\tError: UNKNOWN_PRIVILEGE\n\nOI Privileges not applied.\n\n\nErrors occurred when applying OI Rules: \n\tError: UNKNOWN_PRIVILEGE. Details: Privilege: \"invalidPriv\"\n\nOI Rules not applied.\n\n\nErrors occurred when applying OI Role Mappings: \n\tError: UNKNOWN_ROLE. Details: Role: \"invalidRole\"\n\nOI Role Mappings not applied.\n", errorAssertion: ErrorEqualMsg("error during mutation"), }, "All Mutations, Some Errors": { variables: map[string]interface{}{ "skipCommands": graphqlTypes.Boolean(false), "skipPrivileges": graphqlTypes.Boolean(false), "skipRules": graphqlTypes.Boolean(false), "skipRoleMappings": graphqlTypes.Boolean(false), }, mutation: oiLoadDataMutation{ CreateOperatorInterventionCommands: struct { model.CreateOperatorInterventionCommandResponse }{ CreateOperatorInterventionCommandResponse: model.CreateOperatorInterventionCommandResponse{ Errors: nil, }, }, CreateOperatorInterventionPrivileges: struct { model.CreateOperatorInterventionPrivilegeResponse }{ CreateOperatorInterventionPrivilegeResponse: model.CreateOperatorInterventionPrivilegeResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownPrivilege}, }, }, }, UpdateOperatorInterventionRules: struct { model.UpdateOperatorInterventionRuleResponse }{ UpdateOperatorInterventionRuleResponse: model.UpdateOperatorInterventionRuleResponse{ Errors: nil, }, }, UpdateOperatorInterventionRoleMappings: struct { model.UpdateOperatorInterventionRoleMappingResponse }{ UpdateOperatorInterventionRoleMappingResponse: model.UpdateOperatorInterventionRoleMappingResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownRole, Role: &invalidRole}, }, }, }, }, exp: "\nOI Commands applied\n\n\nErrors occurred when applying OI Privileges: \n\tError: UNKNOWN_PRIVILEGE\n\nOI Privileges not applied.\n\n\nOI Rules applied\n\n\nErrors occurred when applying OI Role Mappings: \n\tError: UNKNOWN_ROLE. Details: Role: \"invalidRole\"\n\nOI Role Mappings not applied.\n", errorAssertion: ErrorEqualMsg("error during mutation"), }, "Some Mutations, No Errors": { variables: map[string]interface{}{ "skipCommands": graphqlTypes.Boolean(false), "skipPrivileges": graphqlTypes.Boolean(true), "skipRules": graphqlTypes.Boolean(false), "skipRoleMappings": graphqlTypes.Boolean(true), }, mutation: oiLoadDataMutation{ CreateOperatorInterventionCommands: struct { model.CreateOperatorInterventionCommandResponse }{ CreateOperatorInterventionCommandResponse: model.CreateOperatorInterventionCommandResponse{ Errors: nil, }, }, CreateOperatorInterventionPrivileges: struct { model.CreateOperatorInterventionPrivilegeResponse }{ CreateOperatorInterventionPrivilegeResponse: model.CreateOperatorInterventionPrivilegeResponse{ Errors: nil, }, }, UpdateOperatorInterventionRules: struct { model.UpdateOperatorInterventionRuleResponse }{ UpdateOperatorInterventionRuleResponse: model.UpdateOperatorInterventionRuleResponse{ Errors: nil, }, }, UpdateOperatorInterventionRoleMappings: struct { model.UpdateOperatorInterventionRoleMappingResponse }{ UpdateOperatorInterventionRoleMappingResponse: model.UpdateOperatorInterventionRoleMappingResponse{ Errors: nil, }, }, }, exp: "\nOI Commands applied\n\n\nOI Rules applied\n", errorAssertion: require.NoError, }, "Some Mutations, All Errors": { variables: map[string]interface{}{ "skipCommands": graphqlTypes.Boolean(true), "skipPrivileges": graphqlTypes.Boolean(false), "skipRules": graphqlTypes.Boolean(false), "skipRoleMappings": graphqlTypes.Boolean(true), }, mutation: oiLoadDataMutation{ CreateOperatorInterventionCommands: struct { model.CreateOperatorInterventionCommandResponse }{ CreateOperatorInterventionCommandResponse: model.CreateOperatorInterventionCommandResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownCommand}, }, }, }, CreateOperatorInterventionPrivileges: struct { model.CreateOperatorInterventionPrivilegeResponse }{ CreateOperatorInterventionPrivilegeResponse: model.CreateOperatorInterventionPrivilegeResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownPrivilege}, }, }, }, UpdateOperatorInterventionRules: struct { model.UpdateOperatorInterventionRuleResponse }{ UpdateOperatorInterventionRuleResponse: model.UpdateOperatorInterventionRuleResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownRule, Privilege: &invalidPriv, Command: &invalidCommand}, }, }, }, UpdateOperatorInterventionRoleMappings: struct { model.UpdateOperatorInterventionRoleMappingResponse }{ UpdateOperatorInterventionRoleMappingResponse: model.UpdateOperatorInterventionRoleMappingResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownRole, Role: &invalidRole}, }, }, }, }, exp: "\nErrors occurred when applying OI Privileges: \n\tError: UNKNOWN_PRIVILEGE\n\nOI Privileges not applied.\n\n\nErrors occurred when applying OI Rules: \n\tError: UNKNOWN_RULE. Details: Privilege: \"invalidPriv\", Command: \"invalidCommand\"\n\nOI Rules not applied.\n", errorAssertion: ErrorEqualMsg("error during mutation"), }, "Some Mutations, Some Errors": { variables: map[string]interface{}{ "skipCommands": graphqlTypes.Boolean(false), "skipPrivileges": graphqlTypes.Boolean(false), "skipRules": graphqlTypes.Boolean(true), "skipRoleMappings": graphqlTypes.Boolean(false), }, mutation: oiLoadDataMutation{ CreateOperatorInterventionCommands: struct { model.CreateOperatorInterventionCommandResponse }{ CreateOperatorInterventionCommandResponse: model.CreateOperatorInterventionCommandResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownPrivilege}, }, }, }, CreateOperatorInterventionPrivileges: struct { model.CreateOperatorInterventionPrivilegeResponse }{ CreateOperatorInterventionPrivilegeResponse: model.CreateOperatorInterventionPrivilegeResponse{ Errors: nil, }, }, UpdateOperatorInterventionRules: struct { model.UpdateOperatorInterventionRuleResponse }{ UpdateOperatorInterventionRuleResponse: model.UpdateOperatorInterventionRuleResponse{ Errors: nil, }, }, UpdateOperatorInterventionRoleMappings: struct { model.UpdateOperatorInterventionRoleMappingResponse }{ UpdateOperatorInterventionRoleMappingResponse: model.UpdateOperatorInterventionRoleMappingResponse{ Errors: []*model.OperatorInterventionErrorResponse{ {Type: model.OperatorInterventionErrorTypeUnknownRole, Role: &invalidRole}, }, }, }, }, exp: "\nErrors occurred when applying OI Commands: \n\tError: UNKNOWN_PRIVILEGE\n\nOI Commands not applied.\n\n\nOI Privileges applied\n\n\nErrors occurred when applying OI Role Mappings: \n\tError: UNKNOWN_ROLE. Details: Role: \"invalidRole\"\n\nOI Role Mappings not applied.\n", errorAssertion: ErrorEqualMsg("error during mutation"), }, } for name, tc := range tests { tc := tc t.Run(name, func(t *testing.T) { t.Parallel() out, err := generateAllOutput(tc.variables, tc.mutation) tc.errorAssertion(t, err) require.Equal(t, tc.exp, out) }) } }