github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/plugin_test.go (about)

     1  /*
     2  Copyright 2023 Gravitational, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package types
    18  
    19  import (
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/gravitational/trace"
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  func TestPluginWithoutSecrets(t *testing.T) {
    28  	spec := PluginSpecV1{
    29  		Settings: &PluginSpecV1_SlackAccessPlugin{
    30  			SlackAccessPlugin: &PluginSlackAccessSettings{
    31  				FallbackChannel: "#access-requests",
    32  			},
    33  		},
    34  	}
    35  
    36  	creds := &PluginCredentialsV1{
    37  		Credentials: &PluginCredentialsV1_Oauth2AccessToken{
    38  			Oauth2AccessToken: &PluginOAuth2AccessTokenCredentials{
    39  				AccessToken:  "access_token",
    40  				RefreshToken: "refresh_token",
    41  				Expires:      time.Now().UTC(),
    42  			},
    43  		},
    44  	}
    45  
    46  	plugin := NewPluginV1(Metadata{Name: "foobar"}, spec, creds)
    47  	plugin = plugin.WithoutSecrets().(*PluginV1)
    48  	require.Nil(t, plugin.Credentials)
    49  }
    50  
    51  func TestPluginOpenAIValidation(t *testing.T) {
    52  	spec := PluginSpecV1{
    53  		Settings: &PluginSpecV1_Openai{},
    54  	}
    55  	testCases := []struct {
    56  		name      string
    57  		creds     *PluginCredentialsV1
    58  		assertErr require.ErrorAssertionFunc
    59  	}{
    60  		{
    61  			name:  "no credentials",
    62  			creds: nil,
    63  			assertErr: func(t require.TestingT, err error, args ...any) {
    64  				require.Error(t, err)
    65  				require.True(t, trace.IsBadParameter(err))
    66  				require.Contains(t, err.Error(), "credentials must be set")
    67  			},
    68  		},
    69  		{
    70  			name:  "no credentials inner",
    71  			creds: &PluginCredentialsV1{},
    72  			assertErr: func(t require.TestingT, err error, args ...any) {
    73  				require.Error(t, err)
    74  				require.True(t, trace.IsBadParameter(err))
    75  				require.Contains(t, err.Error(), "must be used with the bearer token credential type")
    76  			},
    77  		},
    78  		{
    79  			name: "invalid credential type (oauth2)",
    80  			creds: &PluginCredentialsV1{
    81  				Credentials: &PluginCredentialsV1_Oauth2AccessToken{},
    82  			},
    83  			assertErr: func(t require.TestingT, err error, args ...any) {
    84  				require.Error(t, err)
    85  				require.True(t, trace.IsBadParameter(err))
    86  				require.Contains(t, err.Error(), "must be used with the bearer token credential type")
    87  			},
    88  		},
    89  		{
    90  			name: "valid credentials (token)",
    91  			creds: &PluginCredentialsV1{
    92  				Credentials: &PluginCredentialsV1_BearerToken{
    93  					BearerToken: &PluginBearerTokenCredentials{
    94  						Token: "xxx-abc",
    95  					},
    96  				},
    97  			},
    98  			assertErr: func(t require.TestingT, err error, args ...any) {
    99  				require.NoError(t, err)
   100  			},
   101  		},
   102  	}
   103  
   104  	for _, tc := range testCases {
   105  		t.Run(tc.name, func(t *testing.T) {
   106  			plugin := NewPluginV1(Metadata{Name: "foobar"}, spec, tc.creds)
   107  			tc.assertErr(t, plugin.CheckAndSetDefaults())
   108  		})
   109  	}
   110  }
   111  
   112  func TestPluginOpsgenieValidation(t *testing.T) {
   113  	testCases := []struct {
   114  		name      string
   115  		settings  *PluginSpecV1_Opsgenie
   116  		creds     *PluginCredentialsV1
   117  		assertErr require.ErrorAssertionFunc
   118  	}{
   119  		{
   120  			name: "no settings",
   121  			settings: &PluginSpecV1_Opsgenie{
   122  				Opsgenie: nil,
   123  			},
   124  			creds: nil,
   125  			assertErr: func(t require.TestingT, err error, args ...any) {
   126  				require.True(t, trace.IsBadParameter(err))
   127  				require.Contains(t, err.Error(), "missing opsgenie settings")
   128  			},
   129  		},
   130  		{
   131  			name: "no api endpint",
   132  			settings: &PluginSpecV1_Opsgenie{
   133  				Opsgenie: &PluginOpsgenieAccessSettings{},
   134  			},
   135  			creds: nil,
   136  			assertErr: func(t require.TestingT, err error, args ...any) {
   137  				require.True(t, trace.IsBadParameter(err))
   138  				require.Contains(t, err.Error(), "api endpoint url must be set")
   139  			},
   140  		},
   141  		{
   142  			name: "no static credentials",
   143  			settings: &PluginSpecV1_Opsgenie{
   144  				Opsgenie: &PluginOpsgenieAccessSettings{
   145  					ApiEndpoint: "https://test.opsgenie.com",
   146  				},
   147  			},
   148  			assertErr: func(t require.TestingT, err error, args ...any) {
   149  				require.True(t, trace.IsBadParameter(err))
   150  				require.Contains(t, err.Error(), "must be used with the static credentials ref type")
   151  			},
   152  		},
   153  		{
   154  			name: "static credentials labels not defined",
   155  			settings: &PluginSpecV1_Opsgenie{
   156  				Opsgenie: &PluginOpsgenieAccessSettings{
   157  					ApiEndpoint: "https://test.opsgenie.com",
   158  				},
   159  			},
   160  			creds: &PluginCredentialsV1{
   161  				Credentials: &PluginCredentialsV1_StaticCredentialsRef{
   162  					&PluginStaticCredentialsRef{
   163  						Labels: map[string]string{},
   164  					},
   165  				},
   166  			},
   167  			assertErr: func(t require.TestingT, err error, args ...any) {
   168  				require.True(t, trace.IsBadParameter(err))
   169  				require.Contains(t, err.Error(), "labels must be specified")
   170  			},
   171  		},
   172  		{
   173  			name: "valid credentials (static credentials)",
   174  			settings: &PluginSpecV1_Opsgenie{
   175  				Opsgenie: &PluginOpsgenieAccessSettings{
   176  					ApiEndpoint: "https://test.opsgenie.com",
   177  				},
   178  			},
   179  			creds: &PluginCredentialsV1{
   180  				Credentials: &PluginCredentialsV1_StaticCredentialsRef{
   181  					&PluginStaticCredentialsRef{
   182  						Labels: map[string]string{
   183  							"label1": "value1",
   184  						},
   185  					},
   186  				},
   187  			},
   188  			assertErr: func(t require.TestingT, err error, args ...any) {
   189  				require.NoError(t, err)
   190  			},
   191  		},
   192  	}
   193  
   194  	for _, tc := range testCases {
   195  		t.Run(tc.name, func(t *testing.T) {
   196  			plugin := NewPluginV1(Metadata{Name: "foobar"}, PluginSpecV1{
   197  				Settings: tc.settings,
   198  			}, tc.creds)
   199  			tc.assertErr(t, plugin.CheckAndSetDefaults())
   200  		})
   201  	}
   202  }
   203  
   204  func requireBadParameterWith(msg string) require.ErrorAssertionFunc {
   205  	return func(t require.TestingT, err error, args ...interface{}) {
   206  		require.True(t, trace.IsBadParameter(err), "error: %v", err)
   207  		require.Contains(t, err.Error(), msg)
   208  	}
   209  }
   210  
   211  func TestPluginOktaValidation(t *testing.T) {
   212  	validSettings := &PluginSpecV1_Okta{
   213  		Okta: &PluginOktaSettings{
   214  			OrgUrl:         "https://test.okta.com",
   215  			EnableUserSync: true,
   216  			SsoConnectorId: "some-sso-connector-id",
   217  		},
   218  	}
   219  
   220  	validSettingsWithSyncSettings := &PluginSpecV1_Okta{
   221  		Okta: &PluginOktaSettings{
   222  			OrgUrl:         "https://test.okta.com",
   223  			EnableUserSync: true,
   224  			SsoConnectorId: "some-sso-connector-id",
   225  			SyncSettings: &PluginOktaSyncSettings{
   226  				SyncAccessLists: true,
   227  				DefaultOwners:   []string{"owner1"},
   228  			},
   229  		},
   230  	}
   231  
   232  	validCreds := &PluginCredentialsV1{
   233  		Credentials: &PluginCredentialsV1_StaticCredentialsRef{
   234  			&PluginStaticCredentialsRef{
   235  				Labels: map[string]string{
   236  					"label1": "value1",
   237  				},
   238  			},
   239  		},
   240  	}
   241  
   242  	testCases := []struct {
   243  		name        string
   244  		settings    *PluginSpecV1_Okta
   245  		creds       *PluginCredentialsV1
   246  		assertErr   require.ErrorAssertionFunc
   247  		assertValue func(*testing.T, *PluginOktaSettings)
   248  	}{
   249  		{
   250  			name:      "valid values are preserved",
   251  			settings:  validSettings,
   252  			creds:     validCreds,
   253  			assertErr: require.NoError,
   254  			assertValue: func(t *testing.T, settings *PluginOktaSettings) {
   255  				require.Equal(t, "https://test.okta.com", settings.OrgUrl)
   256  				require.True(t, settings.EnableUserSync)
   257  				require.Equal(t, "some-sso-connector-id", settings.SsoConnectorId)
   258  				require.True(t, settings.SyncSettings.SyncUsers)
   259  				require.Equal(t, "some-sso-connector-id", settings.SyncSettings.SsoConnectorId)
   260  				require.False(t, settings.SyncSettings.SyncAccessLists)
   261  			},
   262  		},
   263  		{
   264  			name:      "valid values are preserved, import populated",
   265  			settings:  validSettingsWithSyncSettings,
   266  			creds:     validCreds,
   267  			assertErr: require.NoError,
   268  			assertValue: func(t *testing.T, settings *PluginOktaSettings) {
   269  				require.Equal(t, "https://test.okta.com", settings.OrgUrl)
   270  				require.True(t, settings.EnableUserSync)
   271  				require.False(t, settings.SyncSettings.SyncUsers) // Mismatch because there are sync settings.
   272  				require.True(t, settings.SyncSettings.SyncAccessLists)
   273  				require.ElementsMatch(t, []string{"owner1"}, settings.SyncSettings.DefaultOwners)
   274  			},
   275  		},
   276  		{
   277  			name: "no settings",
   278  			settings: &PluginSpecV1_Okta{
   279  				Okta: nil,
   280  			},
   281  			creds:     validCreds,
   282  			assertErr: requireBadParameterWith("missing Okta settings"),
   283  		},
   284  		{
   285  			name: "no org URL",
   286  			settings: &PluginSpecV1_Okta{
   287  				Okta: &PluginOktaSettings{},
   288  			},
   289  			creds:     validCreds,
   290  			assertErr: requireBadParameterWith("org_url must be set"),
   291  		},
   292  		{
   293  			name: "no credentials inner",
   294  			settings: &PluginSpecV1_Okta{
   295  				Okta: &PluginOktaSettings{
   296  					OrgUrl: "https://test.okta.com",
   297  				},
   298  			},
   299  			creds:     &PluginCredentialsV1{},
   300  			assertErr: requireBadParameterWith("must be used with the static credentials ref type"),
   301  		},
   302  		{
   303  			name: "invalid credential type (oauth2)",
   304  			settings: &PluginSpecV1_Okta{
   305  				Okta: &PluginOktaSettings{
   306  					OrgUrl: "https://test.okta.com",
   307  				},
   308  			},
   309  			creds: &PluginCredentialsV1{
   310  				Credentials: &PluginCredentialsV1_Oauth2AccessToken{},
   311  			},
   312  			assertErr: requireBadParameterWith("must be used with the static credentials ref type"),
   313  		},
   314  		{
   315  			name: "invalid credentials (static credentials)",
   316  			settings: &PluginSpecV1_Okta{
   317  				Okta: &PluginOktaSettings{
   318  					OrgUrl: "https://test.okta.com",
   319  				},
   320  			},
   321  			creds: &PluginCredentialsV1{
   322  				Credentials: &PluginCredentialsV1_StaticCredentialsRef{
   323  					&PluginStaticCredentialsRef{
   324  						Labels: map[string]string{},
   325  					},
   326  				},
   327  			},
   328  			assertErr: requireBadParameterWith("labels must be specified"),
   329  		}, {
   330  			name: "EnableUserSync defaults to false",
   331  			settings: &PluginSpecV1_Okta{
   332  				Okta: &PluginOktaSettings{
   333  					OrgUrl: "https://test.okta.com",
   334  				},
   335  			},
   336  			creds:     validCreds,
   337  			assertErr: require.NoError,
   338  			assertValue: func(t *testing.T, settings *PluginOktaSettings) {
   339  				require.False(t, settings.EnableUserSync)
   340  			},
   341  		}, {
   342  			name: "SSO connector ID required for user sync",
   343  			settings: &PluginSpecV1_Okta{
   344  				Okta: &PluginOktaSettings{
   345  					OrgUrl:         "https://test.okta.com",
   346  					EnableUserSync: true,
   347  				},
   348  			},
   349  			creds:     validCreds,
   350  			assertErr: require.Error,
   351  		}, {
   352  			name: "SSO connector ID not required without user sync",
   353  			settings: &PluginSpecV1_Okta{
   354  				Okta: &PluginOktaSettings{
   355  					OrgUrl:         "https://test.okta.com",
   356  					EnableUserSync: false,
   357  				},
   358  			},
   359  			creds:     validCreds,
   360  			assertErr: require.NoError,
   361  			assertValue: func(t *testing.T, settings *PluginOktaSettings) {
   362  				require.False(t, settings.EnableUserSync)
   363  				require.Empty(t, settings.SsoConnectorId)
   364  				require.False(t, settings.SyncSettings.SyncUsers)
   365  				require.Empty(t, settings.SyncSettings.SsoConnectorId)
   366  			},
   367  		}, {
   368  			name: "import enabled without default owners",
   369  			settings: &PluginSpecV1_Okta{
   370  				Okta: &PluginOktaSettings{
   371  					OrgUrl:         "https://test.okta.com",
   372  					EnableUserSync: true,
   373  					SsoConnectorId: "some-sso-connector-id",
   374  					SyncSettings: &PluginOktaSyncSettings{
   375  						SyncAccessLists: true,
   376  					},
   377  				},
   378  			},
   379  			creds:     validCreds,
   380  			assertErr: requireBadParameterWith("default owners must be set when access list import is enabled"),
   381  		},
   382  	}
   383  
   384  	for _, tc := range testCases {
   385  		t.Run(tc.name, func(t *testing.T) {
   386  			plugin := NewPluginV1(Metadata{Name: "foobar"}, PluginSpecV1{
   387  				Settings: tc.settings,
   388  			}, tc.creds)
   389  			tc.assertErr(t, plugin.CheckAndSetDefaults())
   390  			if tc.assertValue != nil {
   391  				tc.assertValue(t, plugin.Spec.GetOkta())
   392  			}
   393  		})
   394  	}
   395  }
   396  
   397  func TestPluginJamfValidation(t *testing.T) {
   398  	testCases := []struct {
   399  		name      string
   400  		settings  *PluginSpecV1_Jamf
   401  		creds     *PluginCredentialsV1
   402  		assertErr require.ErrorAssertionFunc
   403  	}{
   404  		{
   405  			name: "no settings",
   406  			settings: &PluginSpecV1_Jamf{
   407  				Jamf: nil,
   408  			},
   409  			creds: nil,
   410  			assertErr: func(t require.TestingT, err error, args ...any) {
   411  				require.True(t, trace.IsBadParameter(err))
   412  				require.Contains(t, err.Error(), "missing Jamf settings")
   413  			},
   414  		},
   415  		{
   416  			name: "no api Endpoint",
   417  			settings: &PluginSpecV1_Jamf{
   418  				Jamf: &PluginJamfSettings{
   419  					JamfSpec: &JamfSpecV1{},
   420  				},
   421  			},
   422  			creds: nil,
   423  			assertErr: func(t require.TestingT, err error, args ...any) {
   424  				require.True(t, trace.IsBadParameter(err))
   425  				require.Contains(t, err.Error(), "api endpoint must be set")
   426  			},
   427  		},
   428  		{
   429  			name: "no credentials inner",
   430  			settings: &PluginSpecV1_Jamf{
   431  				Jamf: &PluginJamfSettings{
   432  					JamfSpec: &JamfSpecV1{
   433  						ApiEndpoint: "https://api.testjamfserver.com",
   434  					},
   435  				},
   436  			},
   437  			creds: &PluginCredentialsV1{},
   438  			assertErr: func(t require.TestingT, err error, args ...any) {
   439  				require.True(t, trace.IsBadParameter(err))
   440  				require.Contains(t, err.Error(), "must be used with the static credentials ref type")
   441  			},
   442  		},
   443  		{
   444  			name: "invalid credential type (oauth2)",
   445  			settings: &PluginSpecV1_Jamf{
   446  				Jamf: &PluginJamfSettings{
   447  					JamfSpec: &JamfSpecV1{
   448  						ApiEndpoint: "https://api.testjamfserver.com",
   449  					},
   450  				},
   451  			},
   452  			creds: &PluginCredentialsV1{
   453  				Credentials: &PluginCredentialsV1_Oauth2AccessToken{},
   454  			},
   455  			assertErr: func(t require.TestingT, err error, args ...any) {
   456  				require.True(t, trace.IsBadParameter(err))
   457  				require.Contains(t, err.Error(), "must be used with the static credentials ref type")
   458  			},
   459  		},
   460  		{
   461  			name: "invalid credentials (static credentials)",
   462  			settings: &PluginSpecV1_Jamf{
   463  				Jamf: &PluginJamfSettings{
   464  					JamfSpec: &JamfSpecV1{
   465  						ApiEndpoint: "https://api.testjamfserver.com",
   466  					},
   467  				},
   468  			},
   469  			creds: &PluginCredentialsV1{
   470  				Credentials: &PluginCredentialsV1_StaticCredentialsRef{
   471  					&PluginStaticCredentialsRef{
   472  						Labels: map[string]string{},
   473  					},
   474  				},
   475  			},
   476  			assertErr: func(t require.TestingT, err error, args ...any) {
   477  				require.True(t, trace.IsBadParameter(err))
   478  				require.Contains(t, err.Error(), "labels must be specified")
   479  			},
   480  		},
   481  		{
   482  			name: "valid credentials (static credentials)",
   483  			settings: &PluginSpecV1_Jamf{
   484  				Jamf: &PluginJamfSettings{
   485  					JamfSpec: &JamfSpecV1{
   486  						ApiEndpoint: "https://api.testjamfserver.com",
   487  					},
   488  				},
   489  			},
   490  			creds: &PluginCredentialsV1{
   491  				Credentials: &PluginCredentialsV1_StaticCredentialsRef{
   492  					&PluginStaticCredentialsRef{
   493  						Labels: map[string]string{
   494  							"label1": "value1",
   495  						},
   496  					},
   497  				},
   498  			},
   499  			assertErr: func(t require.TestingT, err error, args ...any) {
   500  				require.NoError(t, err)
   501  			},
   502  		},
   503  	}
   504  
   505  	for _, tc := range testCases {
   506  		t.Run(tc.name, func(t *testing.T) {
   507  			plugin := NewPluginV1(Metadata{Name: "foobar"}, PluginSpecV1{
   508  				Settings: tc.settings,
   509  			}, tc.creds)
   510  			tc.assertErr(t, plugin.CheckAndSetDefaults())
   511  		})
   512  	}
   513  }
   514  
   515  func TestPluginMattermostValidation(t *testing.T) {
   516  	defaultSettings := &PluginSpecV1_Mattermost{
   517  		Mattermost: &PluginMattermostSettings{
   518  			ServerUrl: "https://test.mattermost.com",
   519  			Team:      "team-llama",
   520  			Channel:   "teleport",
   521  		},
   522  	}
   523  
   524  	testCases := []struct {
   525  		name      string
   526  		settings  *PluginSpecV1_Mattermost
   527  		creds     *PluginCredentialsV1
   528  		assertErr require.ErrorAssertionFunc
   529  	}{
   530  		{
   531  			name: "no settings",
   532  			settings: &PluginSpecV1_Mattermost{
   533  				Mattermost: nil,
   534  			},
   535  			creds: nil,
   536  			assertErr: func(t require.TestingT, err error, args ...any) {
   537  				require.True(t, trace.IsBadParameter(err))
   538  				require.Contains(t, err.Error(), "missing Mattermost settings")
   539  			},
   540  		},
   541  		{
   542  			name: "no server url",
   543  			settings: &PluginSpecV1_Mattermost{
   544  				Mattermost: &PluginMattermostSettings{},
   545  			},
   546  			creds: nil,
   547  			assertErr: func(t require.TestingT, err error, args ...any) {
   548  				require.True(t, trace.IsBadParameter(err))
   549  				require.Contains(t, err.Error(), "server url is required")
   550  			},
   551  		},
   552  		{
   553  			name: "no team",
   554  			settings: &PluginSpecV1_Mattermost{
   555  				Mattermost: &PluginMattermostSettings{
   556  					ServerUrl: "https://test.mattermost.com",
   557  					Channel:   "some-channel",
   558  				},
   559  			},
   560  			creds: nil,
   561  			assertErr: func(t require.TestingT, err error, args ...any) {
   562  				require.True(t, trace.IsBadParameter(err))
   563  				require.Contains(t, err.Error(), "team is required")
   564  			},
   565  		},
   566  		{
   567  			name: "no channel",
   568  			settings: &PluginSpecV1_Mattermost{
   569  				Mattermost: &PluginMattermostSettings{
   570  					ServerUrl: "https://test.mattermost.com",
   571  					Team:      "team-llama",
   572  				},
   573  			},
   574  			creds: nil,
   575  			assertErr: func(t require.TestingT, err error, args ...any) {
   576  				require.True(t, trace.IsBadParameter(err))
   577  				require.Contains(t, err.Error(), "channel is required")
   578  			},
   579  		},
   580  		{
   581  			name:     "no credentials inner",
   582  			settings: defaultSettings,
   583  			creds:    &PluginCredentialsV1{},
   584  			assertErr: func(t require.TestingT, err error, args ...any) {
   585  				require.True(t, trace.IsBadParameter(err))
   586  				require.Contains(t, err.Error(), "must be used with the static credentials ref type")
   587  			},
   588  		},
   589  		{
   590  			name:     "invalid credential type (oauth2)",
   591  			settings: defaultSettings,
   592  			creds: &PluginCredentialsV1{
   593  				Credentials: &PluginCredentialsV1_Oauth2AccessToken{},
   594  			},
   595  			assertErr: func(t require.TestingT, err error, args ...any) {
   596  				require.True(t, trace.IsBadParameter(err))
   597  				require.Contains(t, err.Error(), "must be used with the static credentials ref type")
   598  			},
   599  		},
   600  		{
   601  			name:     "no labels for credentials",
   602  			settings: defaultSettings,
   603  			creds: &PluginCredentialsV1{
   604  				Credentials: &PluginCredentialsV1_StaticCredentialsRef{
   605  					&PluginStaticCredentialsRef{
   606  						Labels: map[string]string{},
   607  					},
   608  				},
   609  			},
   610  			assertErr: func(t require.TestingT, err error, args ...any) {
   611  				require.True(t, trace.IsBadParameter(err))
   612  				require.Contains(t, err.Error(), "labels must be specified")
   613  			},
   614  		},
   615  		{
   616  			name:     "valid settings with team/channel",
   617  			settings: defaultSettings,
   618  			creds: &PluginCredentialsV1{
   619  				Credentials: &PluginCredentialsV1_StaticCredentialsRef{
   620  					&PluginStaticCredentialsRef{
   621  						Labels: map[string]string{
   622  							"label1": "value1",
   623  						},
   624  					},
   625  				},
   626  			},
   627  			assertErr: func(t require.TestingT, err error, args ...any) {
   628  				require.NoError(t, err)
   629  			},
   630  		},
   631  		{
   632  			name: "valid settings with no team/channel",
   633  			settings: &PluginSpecV1_Mattermost{
   634  				Mattermost: &PluginMattermostSettings{
   635  					ServerUrl: "https://test.mattermost.com",
   636  				},
   637  			},
   638  			creds: &PluginCredentialsV1{
   639  				Credentials: &PluginCredentialsV1_StaticCredentialsRef{
   640  					&PluginStaticCredentialsRef{
   641  						Labels: map[string]string{
   642  							"label1": "value1",
   643  						},
   644  					},
   645  				},
   646  			},
   647  			assertErr: func(t require.TestingT, err error, args ...any) {
   648  				require.NoError(t, err)
   649  			},
   650  		},
   651  	}
   652  
   653  	for _, tc := range testCases {
   654  		t.Run(tc.name, func(t *testing.T) {
   655  			plugin := NewPluginV1(Metadata{Name: "foobar"}, PluginSpecV1{
   656  				Settings: tc.settings,
   657  			}, tc.creds)
   658  			tc.assertErr(t, plugin.CheckAndSetDefaults())
   659  		})
   660  	}
   661  }
   662  
   663  func requireBadParameterError(t require.TestingT, err error, args ...any) {
   664  	if tt, ok := t.(*testing.T); ok {
   665  		tt.Helper()
   666  	}
   667  	require.Error(t, err)
   668  	require.True(t, trace.IsBadParameter(err), args...)
   669  }
   670  
   671  func requireNamedBadParameterError(name string) require.ErrorAssertionFunc {
   672  	return func(t require.TestingT, err error, args ...any) {
   673  		if tt, ok := t.(*testing.T); ok {
   674  			tt.Helper()
   675  		}
   676  		require.ErrorContains(t, err, name)
   677  		require.True(t, trace.IsBadParameter(err))
   678  	}
   679  }
   680  
   681  func TestPluginJiraValidation(t *testing.T) {
   682  	validSettings := func() *PluginSpecV1_Jira {
   683  		return &PluginSpecV1_Jira{
   684  			&PluginJiraSettings{
   685  				ServerUrl:  "https://example.com",
   686  				ProjectKey: "PRJ",
   687  				IssueType:  "Task",
   688  			},
   689  		}
   690  	}
   691  	validCreds := func() *PluginCredentialsV1 {
   692  		return &PluginCredentialsV1{
   693  			Credentials: &PluginCredentialsV1_StaticCredentialsRef{
   694  				&PluginStaticCredentialsRef{
   695  					Labels: map[string]string{
   696  						"jira/address":   "https://jira.example.com",
   697  						"jira/project":   "PRJ",
   698  						"jira/issueType": "Task",
   699  					},
   700  				},
   701  			},
   702  		}
   703  	}
   704  
   705  	testCases := []struct {
   706  		name           string
   707  		mutateSettings func(*PluginSpecV1_Jira)
   708  		mutateCreds    func(*PluginCredentialsV1)
   709  		assertErr      require.ErrorAssertionFunc
   710  	}{
   711  		{
   712  			name:      "Valid",
   713  			assertErr: require.NoError,
   714  		}, {
   715  			name:           "Missing Settings",
   716  			mutateSettings: func(s *PluginSpecV1_Jira) { s.Jira = nil },
   717  			assertErr:      requireBadParameterError,
   718  		}, {
   719  			name:           "Missing Server URL",
   720  			mutateSettings: func(s *PluginSpecV1_Jira) { s.Jira.ServerUrl = "" },
   721  			assertErr:      requireNamedBadParameterError("server URL"),
   722  		}, {
   723  			name:           "Missing Project Key",
   724  			mutateSettings: func(s *PluginSpecV1_Jira) { s.Jira.ProjectKey = "" },
   725  			assertErr:      requireNamedBadParameterError("project key"),
   726  		}, {
   727  			name:           "Missing Issue Type",
   728  			mutateSettings: func(s *PluginSpecV1_Jira) { s.Jira.IssueType = "" },
   729  			assertErr:      requireNamedBadParameterError("issue type"),
   730  		}, {
   731  			name:        "Missing Credentials",
   732  			mutateCreds: func(c *PluginCredentialsV1) { c.Credentials = nil },
   733  			assertErr:   requireBadParameterError,
   734  		}, {
   735  			name: "Missing Credential Labels",
   736  			mutateCreds: func(c *PluginCredentialsV1) {
   737  				c.Credentials.(*PluginCredentialsV1_StaticCredentialsRef).
   738  					StaticCredentialsRef.
   739  					Labels = map[string]string{}
   740  			},
   741  			assertErr: requireNamedBadParameterError("labels"),
   742  		}, {
   743  			name: "Invalid Credential Type",
   744  			mutateCreds: func(c *PluginCredentialsV1) {
   745  				c.Credentials = &PluginCredentialsV1_Oauth2AccessToken{}
   746  			},
   747  			assertErr: requireNamedBadParameterError("static credentials"),
   748  		},
   749  	}
   750  
   751  	for _, tc := range testCases {
   752  		t.Run(tc.name, func(t *testing.T) {
   753  			settings := validSettings()
   754  			if tc.mutateSettings != nil {
   755  				tc.mutateSettings(settings)
   756  			}
   757  
   758  			creds := validCreds()
   759  			if tc.mutateCreds != nil {
   760  				tc.mutateCreds(creds)
   761  			}
   762  
   763  			plugin := NewPluginV1(Metadata{Name: "uut"}, PluginSpecV1{
   764  				Settings: settings,
   765  			}, creds)
   766  			tc.assertErr(t, plugin.CheckAndSetDefaults())
   767  		})
   768  	}
   769  }
   770  
   771  func TestPluginDiscordValidation(t *testing.T) {
   772  	validSettings := func() *PluginSpecV1_Discord {
   773  		return &PluginSpecV1_Discord{
   774  			&PluginDiscordSettings{
   775  				RoleToRecipients: map[string]*DiscordChannels{
   776  					"*": {ChannelIds: []string{"1234567890"}},
   777  				},
   778  			},
   779  		}
   780  	}
   781  	validCreds := func() *PluginCredentialsV1 {
   782  		return &PluginCredentialsV1{
   783  			Credentials: &PluginCredentialsV1_StaticCredentialsRef{
   784  				&PluginStaticCredentialsRef{
   785  					Labels: map[string]string{},
   786  				},
   787  			},
   788  		}
   789  	}
   790  
   791  	testCases := []struct {
   792  		name           string
   793  		mutateSettings func(*PluginSpecV1_Discord)
   794  		mutateCreds    func(*PluginCredentialsV1)
   795  		assertErr      require.ErrorAssertionFunc
   796  	}{
   797  		{
   798  			name:      "Valid",
   799  			assertErr: require.NoError,
   800  		}, {
   801  			name:           "Missing Settings",
   802  			mutateSettings: func(s *PluginSpecV1_Discord) { s.Discord = nil },
   803  			assertErr:      requireBadParameterError,
   804  		}, {
   805  			name: "Empty Role Mapping",
   806  			mutateSettings: func(s *PluginSpecV1_Discord) {
   807  				s.Discord.RoleToRecipients = map[string]*DiscordChannels{}
   808  			},
   809  			assertErr: requireNamedBadParameterError("role_to_recipients"),
   810  		}, {
   811  			name: "Missing Default Mapping",
   812  			mutateSettings: func(s *PluginSpecV1_Discord) {
   813  				delete(s.Discord.RoleToRecipients, Wildcard)
   814  				s.Discord.RoleToRecipients["access"] = &DiscordChannels{
   815  					ChannelIds: []string{"1234567890"},
   816  				}
   817  			},
   818  			assertErr: requireNamedBadParameterError("default entry"),
   819  		}, {
   820  			name:        "Missing Credentials",
   821  			mutateCreds: func(c *PluginCredentialsV1) { c.Credentials = nil },
   822  			assertErr:   requireBadParameterError,
   823  		}, {
   824  			name: "Invalid Credential Type",
   825  			mutateCreds: func(c *PluginCredentialsV1) {
   826  				c.Credentials = &PluginCredentialsV1_Oauth2AccessToken{}
   827  			},
   828  			assertErr: requireNamedBadParameterError("static credentials"),
   829  		},
   830  	}
   831  
   832  	for _, tc := range testCases {
   833  		t.Run(tc.name, func(t *testing.T) {
   834  			settings := validSettings()
   835  			if tc.mutateSettings != nil {
   836  				tc.mutateSettings(settings)
   837  			}
   838  
   839  			creds := validCreds()
   840  			if tc.mutateCreds != nil {
   841  				tc.mutateCreds(creds)
   842  			}
   843  
   844  			plugin := NewPluginV1(
   845  				Metadata{Name: "uut"},
   846  				PluginSpecV1{Settings: settings},
   847  				creds)
   848  			tc.assertErr(t, plugin.CheckAndSetDefaults())
   849  		})
   850  	}
   851  }
   852  
   853  func TestPluginEntraIDValidation(t *testing.T) {
   854  	validSettings := func() *PluginSpecV1_EntraId {
   855  		return &PluginSpecV1_EntraId{
   856  			EntraId: &PluginEntraIDSettings{
   857  				SyncSettings: &PluginEntraIDSyncSettings{
   858  					DefaultOwners: []string{"admin"},
   859  				},
   860  			},
   861  		}
   862  	}
   863  	testCases := []struct {
   864  		name           string
   865  		mutateSettings func(*PluginSpecV1_EntraId)
   866  		assertErr      require.ErrorAssertionFunc
   867  	}{
   868  		{
   869  			name:           "valid",
   870  			mutateSettings: nil,
   871  			assertErr:      require.NoError,
   872  		},
   873  		{
   874  			name: "missing sync settings",
   875  			mutateSettings: func(s *PluginSpecV1_EntraId) {
   876  				s.EntraId.SyncSettings = nil
   877  			},
   878  			assertErr: requireNamedBadParameterError("sync_settings"),
   879  		},
   880  		{
   881  			name: "missing default owners",
   882  			mutateSettings: func(s *PluginSpecV1_EntraId) {
   883  				s.EntraId.SyncSettings.DefaultOwners = nil
   884  			},
   885  			assertErr: requireNamedBadParameterError("sync_settings.default_owners"),
   886  		},
   887  		{
   888  			name: "empty default owners",
   889  			mutateSettings: func(s *PluginSpecV1_EntraId) {
   890  				s.EntraId.SyncSettings.DefaultOwners = []string{}
   891  			},
   892  			assertErr: requireNamedBadParameterError("sync_settings.default_owners"),
   893  		},
   894  	}
   895  
   896  	for _, tc := range testCases {
   897  		t.Run(tc.name, func(t *testing.T) {
   898  			settings := validSettings()
   899  			if tc.mutateSettings != nil {
   900  				tc.mutateSettings(settings)
   901  			}
   902  
   903  			plugin := NewPluginV1(
   904  				Metadata{Name: "uut"},
   905  				PluginSpecV1{Settings: settings},
   906  				nil)
   907  			tc.assertErr(t, plugin.CheckAndSetDefaults())
   908  		})
   909  	}
   910  }