package settings_test import ( "bytes" "encoding/json" "fmt" "strings" "testing" validation "github.com/go-ozzo/ozzo-validation/v4" "github.com/pocketbase/pocketbase/models/settings" "github.com/pocketbase/pocketbase/tools/auth" "github.com/pocketbase/pocketbase/tools/mailer" ) func TestSettingsValidate(t *testing.T) { s := settings.New() // set invalid settings data s.Meta.AppName = "" s.Logs.MaxDays = -10 s.Smtp.Enabled = true s.Smtp.Host = "" s.S3.Enabled = true s.S3.Endpoint = "invalid" s.AdminAuthToken.Duration = -10 s.AdminPasswordResetToken.Duration = -10 s.AdminFileToken.Duration = -10 s.RecordAuthToken.Duration = -10 s.RecordPasswordResetToken.Duration = -10 s.RecordEmailChangeToken.Duration = -10 s.RecordVerificationToken.Duration = -10 s.RecordFileToken.Duration = -10 s.GoogleAuth.Enabled = true s.GoogleAuth.ClientId = "" s.FacebookAuth.Enabled = true s.FacebookAuth.ClientId = "" s.GithubAuth.Enabled = true s.GithubAuth.ClientId = "" s.GitlabAuth.Enabled = true s.GitlabAuth.ClientId = "" s.DiscordAuth.Enabled = true s.DiscordAuth.ClientId = "" s.TwitterAuth.Enabled = true s.TwitterAuth.ClientId = "" s.MicrosoftAuth.Enabled = true s.MicrosoftAuth.ClientId = "" s.SpotifyAuth.Enabled = true s.SpotifyAuth.ClientId = "" s.KakaoAuth.Enabled = true s.KakaoAuth.ClientId = "" s.TwitchAuth.Enabled = true s.TwitchAuth.ClientId = "" s.StravaAuth.Enabled = true s.StravaAuth.ClientId = "" s.GiteeAuth.Enabled = true s.GiteeAuth.ClientId = "" s.LivechatAuth.Enabled = true s.LivechatAuth.ClientId = "" s.GiteaAuth.Enabled = true s.GiteaAuth.ClientId = "" s.OIDCAuth.Enabled = true s.OIDCAuth.ClientId = "" s.OIDC2Auth.Enabled = true s.OIDC2Auth.ClientId = "" s.OIDC3Auth.Enabled = true s.OIDC3Auth.ClientId = "" s.AppleAuth.Enabled = true s.AppleAuth.ClientId = "" s.InstagramAuth.Enabled = true s.InstagramAuth.ClientId = "" // check if Validate() is triggering the members validate methods. err := s.Validate() if err == nil { t.Fatalf("Expected error, got nil") } expectations := []string{ `"meta":{`, `"logs":{`, `"smtp":{`, `"s3":{`, `"adminAuthToken":{`, `"adminPasswordResetToken":{`, `"adminFileToken":{`, `"recordAuthToken":{`, `"recordPasswordResetToken":{`, `"recordEmailChangeToken":{`, `"recordVerificationToken":{`, `"recordFileToken":{`, `"googleAuth":{`, `"facebookAuth":{`, `"githubAuth":{`, `"gitlabAuth":{`, `"discordAuth":{`, `"twitterAuth":{`, `"microsoftAuth":{`, `"spotifyAuth":{`, `"kakaoAuth":{`, `"twitchAuth":{`, `"stravaAuth":{`, `"giteeAuth":{`, `"livechatAuth":{`, `"giteaAuth":{`, `"oidcAuth":{`, `"oidc2Auth":{`, `"oidc3Auth":{`, `"appleAuth":{`, `"instagramAuth":{`, } errBytes, _ := json.Marshal(err) jsonErr := string(errBytes) for _, expected := range expectations { if !strings.Contains(jsonErr, expected) { t.Errorf("Expected error key %s in %v", expected, jsonErr) } } } func TestSettingsMerge(t *testing.T) { s1 := settings.New() s1.Meta.AppUrl = "old_app_url" s2 := settings.New() s2.Meta.AppName = "test" s2.Logs.MaxDays = 123 s2.Smtp.Host = "test" s2.Smtp.Enabled = true s2.S3.Enabled = true s2.S3.Endpoint = "test" s2.Backups.Cron = "* * * * *" s2.AdminAuthToken.Duration = 1 s2.AdminPasswordResetToken.Duration = 2 s2.AdminFileToken.Duration = 2 s2.RecordAuthToken.Duration = 3 s2.RecordPasswordResetToken.Duration = 4 s2.RecordEmailChangeToken.Duration = 5 s2.RecordVerificationToken.Duration = 6 s2.RecordFileToken.Duration = 7 s2.GoogleAuth.Enabled = true s2.GoogleAuth.ClientId = "google_test" s2.FacebookAuth.Enabled = true s2.FacebookAuth.ClientId = "facebook_test" s2.GithubAuth.Enabled = true s2.GithubAuth.ClientId = "github_test" s2.GitlabAuth.Enabled = true s2.GitlabAuth.ClientId = "gitlab_test" s2.DiscordAuth.Enabled = true s2.DiscordAuth.ClientId = "discord_test" s2.TwitterAuth.Enabled = true s2.TwitterAuth.ClientId = "twitter_test" s2.MicrosoftAuth.Enabled = true s2.MicrosoftAuth.ClientId = "microsoft_test" s2.SpotifyAuth.Enabled = true s2.SpotifyAuth.ClientId = "spotify_test" s2.KakaoAuth.Enabled = true s2.KakaoAuth.ClientId = "kakao_test" s2.TwitchAuth.Enabled = true s2.TwitchAuth.ClientId = "twitch_test" s2.StravaAuth.Enabled = true s2.StravaAuth.ClientId = "strava_test" s2.GiteeAuth.Enabled = true s2.GiteeAuth.ClientId = "gitee_test" s2.LivechatAuth.Enabled = true s2.LivechatAuth.ClientId = "livechat_test" s2.GiteaAuth.Enabled = true s2.GiteaAuth.ClientId = "gitea_test" s2.OIDCAuth.Enabled = true s2.OIDCAuth.ClientId = "oidc_test" s2.OIDC2Auth.Enabled = true s2.OIDC2Auth.ClientId = "oidc2_test" s2.OIDC3Auth.Enabled = true s2.OIDC3Auth.ClientId = "oidc3_test" s2.AppleAuth.Enabled = true s2.AppleAuth.ClientId = "apple_test" s2.InstagramAuth.Enabled = true s2.InstagramAuth.ClientId = "instagram_test" if err := s1.Merge(s2); err != nil { t.Fatal(err) } s1Encoded, err := json.Marshal(s1) if err != nil { t.Fatal(err) } s2Encoded, err := json.Marshal(s2) if err != nil { t.Fatal(err) } if string(s1Encoded) != string(s2Encoded) { t.Fatalf("Expected the same serialization, got %v VS %v", string(s1Encoded), string(s2Encoded)) } } func TestSettingsClone(t *testing.T) { s1 := settings.New() s2, err := s1.Clone() if err != nil { t.Fatal(err) } s1Bytes, err := json.Marshal(s1) if err != nil { t.Fatal(err) } s2Bytes, err := json.Marshal(s2) if err != nil { t.Fatal(err) } if string(s1Bytes) != string(s2Bytes) { t.Fatalf("Expected equivalent serialization, got %v VS %v", string(s1Bytes), string(s2Bytes)) } // verify that it is a deep copy s1.Meta.AppName = "new" if s1.Meta.AppName == s2.Meta.AppName { t.Fatalf("Expected s1 and s2 to have different Meta.AppName, got %s", s1.Meta.AppName) } } func TestSettingsRedactClone(t *testing.T) { testSecret := "test_secret" s1 := settings.New() // control fields s1.Meta.AppName = "test123" // secrets s1.Smtp.Password = testSecret s1.S3.Secret = testSecret s1.Backups.S3.Secret = testSecret s1.AdminAuthToken.Secret = testSecret s1.AdminPasswordResetToken.Secret = testSecret s1.AdminFileToken.Secret = testSecret s1.RecordAuthToken.Secret = testSecret s1.RecordPasswordResetToken.Secret = testSecret s1.RecordEmailChangeToken.Secret = testSecret s1.RecordVerificationToken.Secret = testSecret s1.RecordFileToken.Secret = testSecret s1.GoogleAuth.ClientSecret = testSecret s1.FacebookAuth.ClientSecret = testSecret s1.GithubAuth.ClientSecret = testSecret s1.GitlabAuth.ClientSecret = testSecret s1.DiscordAuth.ClientSecret = testSecret s1.TwitterAuth.ClientSecret = testSecret s1.MicrosoftAuth.ClientSecret = testSecret s1.SpotifyAuth.ClientSecret = testSecret s1.KakaoAuth.ClientSecret = testSecret s1.TwitchAuth.ClientSecret = testSecret s1.StravaAuth.ClientSecret = testSecret s1.GiteeAuth.ClientSecret = testSecret s1.LivechatAuth.ClientSecret = testSecret s1.GiteaAuth.ClientSecret = testSecret s1.OIDCAuth.ClientSecret = testSecret s1.OIDC2Auth.ClientSecret = testSecret s1.OIDC3Auth.ClientSecret = testSecret s1.AppleAuth.ClientSecret = testSecret s1.InstagramAuth.ClientSecret = testSecret s1Bytes, err := json.Marshal(s1) if err != nil { t.Fatal(err) } s2, err := s1.RedactClone() if err != nil { t.Fatal(err) } s2Bytes, err := json.Marshal(s2) if err != nil { t.Fatal(err) } if bytes.Equal(s1Bytes, s2Bytes) { t.Fatalf("Expected the 2 settings to differ, got \n%s", s2Bytes) } if strings.Contains(string(s2Bytes), testSecret) { t.Fatalf("Expected %q secret to be replaced with mask, got \n%s", testSecret, s2Bytes) } if !strings.Contains(string(s2Bytes), settings.SecretMask) { t.Fatalf("Expected the secrets to be replaced with the secret mask, got \n%s", s2Bytes) } if !strings.Contains(string(s2Bytes), `"appName":"test123"`) { t.Fatalf("Missing control field in \n%s", s2Bytes) } } func TestNamedAuthProviderConfigs(t *testing.T) { s := settings.New() s.GoogleAuth.ClientId = "google_test" s.FacebookAuth.ClientId = "facebook_test" s.GithubAuth.ClientId = "github_test" s.GitlabAuth.ClientId = "gitlab_test" s.GitlabAuth.Enabled = true // control s.DiscordAuth.ClientId = "discord_test" s.TwitterAuth.ClientId = "twitter_test" s.MicrosoftAuth.ClientId = "microsoft_test" s.SpotifyAuth.ClientId = "spotify_test" s.KakaoAuth.ClientId = "kakao_test" s.TwitchAuth.ClientId = "twitch_test" s.StravaAuth.ClientId = "strava_test" s.GiteeAuth.ClientId = "gitee_test" s.LivechatAuth.ClientId = "livechat_test" s.GiteaAuth.ClientId = "gitea_test" s.OIDCAuth.ClientId = "oidc_test" s.OIDC2Auth.ClientId = "oidc2_test" s.OIDC3Auth.ClientId = "oidc3_test" s.AppleAuth.ClientId = "apple_test" s.InstagramAuth.ClientId = "instagram_test" result := s.NamedAuthProviderConfigs() encoded, err := json.Marshal(result) if err != nil { t.Fatal(err) } encodedStr := string(encoded) expectedParts := []string{ `"discord":{"enabled":false,"clientId":"discord_test"`, `"facebook":{"enabled":false,"clientId":"facebook_test"`, `"github":{"enabled":false,"clientId":"github_test"`, `"gitlab":{"enabled":true,"clientId":"gitlab_test"`, `"google":{"enabled":false,"clientId":"google_test"`, `"microsoft":{"enabled":false,"clientId":"microsoft_test"`, `"spotify":{"enabled":false,"clientId":"spotify_test"`, `"twitter":{"enabled":false,"clientId":"twitter_test"`, `"kakao":{"enabled":false,"clientId":"kakao_test"`, `"twitch":{"enabled":false,"clientId":"twitch_test"`, `"strava":{"enabled":false,"clientId":"strava_test"`, `"gitee":{"enabled":false,"clientId":"gitee_test"`, `"livechat":{"enabled":false,"clientId":"livechat_test"`, `"gitea":{"enabled":false,"clientId":"gitea_test"`, `"oidc":{"enabled":false,"clientId":"oidc_test"`, `"oidc2":{"enabled":false,"clientId":"oidc2_test"`, `"oidc3":{"enabled":false,"clientId":"oidc3_test"`, `"apple":{"enabled":false,"clientId":"apple_test"`, `"instagram":{"enabled":false,"clientId":"instagram_test"`, } for _, p := range expectedParts { if !strings.Contains(encodedStr, p) { t.Fatalf("Expected \n%s \nin \n%s", p, encodedStr) } } } func TestTokenConfigValidate(t *testing.T) { scenarios := []struct { config settings.TokenConfig expectError bool }{ // zero values { settings.TokenConfig{}, true, }, // invalid data { settings.TokenConfig{ Secret: strings.Repeat("a", 5), Duration: 4, }, true, }, // valid secret but invalid duration { settings.TokenConfig{ Secret: strings.Repeat("a", 30), Duration: 63072000 + 1, }, true, }, // valid data { settings.TokenConfig{ Secret: strings.Repeat("a", 30), Duration: 100, }, false, }, } for i, scenario := range scenarios { result := scenario.config.Validate() if result != nil && !scenario.expectError { t.Errorf("(%d) Didn't expect error, got %v", i, result) } if result == nil && scenario.expectError { t.Errorf("(%d) Expected error, got nil", i) } } } func TestSmtpConfigValidate(t *testing.T) { scenarios := []struct { config settings.SmtpConfig expectError bool }{ // zero values (disabled) { settings.SmtpConfig{}, false, }, // zero values (enabled) { settings.SmtpConfig{Enabled: true}, true, }, // invalid data { settings.SmtpConfig{ Enabled: true, Host: "test:test:test", Port: -10, }, true, }, // invalid auth method { settings.SmtpConfig{ Enabled: true, Host: "example.com", Port: 100, AuthMethod: "example", }, true, }, // valid data (no explicit auth method) { settings.SmtpConfig{ Enabled: true, Host: "example.com", Port: 100, Tls: true, }, false, }, // valid data (explicit auth method - login) { settings.SmtpConfig{ Enabled: true, Host: "example.com", Port: 100, AuthMethod: mailer.SmtpAuthLogin, }, false, }, } for i, scenario := range scenarios { result := scenario.config.Validate() if result != nil && !scenario.expectError { t.Errorf("(%d) Didn't expect error, got %v", i, result) } if result == nil && scenario.expectError { t.Errorf("(%d) Expected error, got nil", i) } } } func TestS3ConfigValidate(t *testing.T) { scenarios := []struct { config settings.S3Config expectError bool }{ // zero values (disabled) { settings.S3Config{}, false, }, // zero values (enabled) { settings.S3Config{Enabled: true}, true, }, // invalid data { settings.S3Config{ Enabled: true, Endpoint: "test:test:test", }, true, }, // valid data (url endpoint) { settings.S3Config{ Enabled: true, Endpoint: "https://localhost:8090", Bucket: "test", Region: "test", AccessKey: "test", Secret: "test", }, false, }, // valid data (hostname endpoint) { settings.S3Config{ Enabled: true, Endpoint: "example.com", Bucket: "test", Region: "test", AccessKey: "test", Secret: "test", }, false, }, } for i, scenario := range scenarios { result := scenario.config.Validate() if result != nil && !scenario.expectError { t.Errorf("(%d) Didn't expect error, got %v", i, result) } if result == nil && scenario.expectError { t.Errorf("(%d) Expected error, got nil", i) } } } func TestMetaConfigValidate(t *testing.T) { invalidTemplate := settings.EmailTemplate{ Subject: "test", ActionUrl: "test", Body: "test", } noPlaceholdersTemplate := settings.EmailTemplate{ Subject: "test", ActionUrl: "http://example.com", Body: "test", } withPlaceholdersTemplate := settings.EmailTemplate{ Subject: "test", ActionUrl: "http://example.com" + settings.EmailPlaceholderToken, Body: "test" + settings.EmailPlaceholderActionUrl, } scenarios := []struct { config settings.MetaConfig expectError bool }{ // zero values { settings.MetaConfig{}, true, }, // invalid data { settings.MetaConfig{ AppName: strings.Repeat("a", 300), AppUrl: "test", SenderName: strings.Repeat("a", 300), SenderAddress: "invalid_email", VerificationTemplate: invalidTemplate, ResetPasswordTemplate: invalidTemplate, ConfirmEmailChangeTemplate: invalidTemplate, }, true, }, // invalid data (missing required placeholders) { settings.MetaConfig{ AppName: "test", AppUrl: "https://example.com", SenderName: "test", SenderAddress: "test@example.com", VerificationTemplate: noPlaceholdersTemplate, ResetPasswordTemplate: noPlaceholdersTemplate, ConfirmEmailChangeTemplate: noPlaceholdersTemplate, }, true, }, // valid data { settings.MetaConfig{ AppName: "test", AppUrl: "https://example.com", SenderName: "test", SenderAddress: "test@example.com", VerificationTemplate: withPlaceholdersTemplate, ResetPasswordTemplate: withPlaceholdersTemplate, ConfirmEmailChangeTemplate: withPlaceholdersTemplate, }, false, }, } for i, scenario := range scenarios { result := scenario.config.Validate() if result != nil && !scenario.expectError { t.Errorf("(%d) Didn't expect error, got %v", i, result) } if result == nil && scenario.expectError { t.Errorf("(%d) Expected error, got nil", i) } } } func TestBackupsConfigValidate(t *testing.T) { scenarios := []struct { name string config settings.BackupsConfig expectedErrors []string }{ { "zero value", settings.BackupsConfig{}, []string{}, }, { "invalid cron", settings.BackupsConfig{ Cron: "invalid", CronMaxKeep: 0, }, []string{"cron", "cronMaxKeep"}, }, { "invalid enabled S3", settings.BackupsConfig{ S3: settings.S3Config{ Enabled: true, }, }, []string{"s3"}, }, { "valid data", settings.BackupsConfig{ S3: settings.S3Config{ Enabled: true, Endpoint: "example.com", Bucket: "test", Region: "test", AccessKey: "test", Secret: "test", }, Cron: "*/10 * * * *", CronMaxKeep: 1, }, []string{}, }, } for _, s := range scenarios { result := s.config.Validate() // parse errors errs, ok := result.(validation.Errors) if !ok && result != nil { t.Errorf("[%s] Failed to parse errors %v", s.name, result) continue } // check errors if len(errs) > len(s.expectedErrors) { t.Errorf("[%s] Expected error keys %v, got %v", s.name, s.expectedErrors, errs) } for _, k := range s.expectedErrors { if _, ok := errs[k]; !ok { t.Errorf("[%s] Missing expected error key %q in %v", s.name, k, errs) } } } } func TestEmailTemplateValidate(t *testing.T) { scenarios := []struct { emailTemplate settings.EmailTemplate expectedErrors []string }{ // require values { settings.EmailTemplate{}, []string{"subject", "actionUrl", "body"}, }, // missing placeholders { settings.EmailTemplate{ Subject: "test", ActionUrl: "test", Body: "test", }, []string{"actionUrl", "body"}, }, // valid data { settings.EmailTemplate{ Subject: "test", ActionUrl: "test" + settings.EmailPlaceholderToken, Body: "test" + settings.EmailPlaceholderActionUrl, }, []string{}, }, } for i, s := range scenarios { result := s.emailTemplate.Validate() // parse errors errs, ok := result.(validation.Errors) if !ok && result != nil { t.Errorf("(%d) Failed to parse errors %v", i, result) continue } // check errors if len(errs) > len(s.expectedErrors) { t.Errorf("(%d) Expected error keys %v, got %v", i, s.expectedErrors, errs) } for _, k := range s.expectedErrors { if _, ok := errs[k]; !ok { t.Errorf("(%d) Missing expected error key %q in %v", i, k, errs) } } } } func TestEmailTemplateResolve(t *testing.T) { allPlaceholders := settings.EmailPlaceholderActionUrl + settings.EmailPlaceholderToken + settings.EmailPlaceholderAppName + settings.EmailPlaceholderAppUrl scenarios := []struct { emailTemplate settings.EmailTemplate expectedSubject string expectedBody string expectedActionUrl string }{ // no placeholders { emailTemplate: settings.EmailTemplate{ Subject: "subject:", Body: "body:", ActionUrl: "/actionUrl////", }, expectedSubject: "subject:", expectedActionUrl: "/actionUrl/", expectedBody: "body:", }, // with placeholders { emailTemplate: settings.EmailTemplate{ ActionUrl: "/actionUrl////" + allPlaceholders, Subject: "subject:" + allPlaceholders, Body: "body:" + allPlaceholders, }, expectedActionUrl: fmt.Sprintf( "/actionUrl/%%7BACTION_URL%%7D%s%s%s", "token_test", "name_test", "url_test", ), expectedSubject: fmt.Sprintf( "subject:%s%s%s%s", settings.EmailPlaceholderActionUrl, settings.EmailPlaceholderToken, "name_test", "url_test", ), expectedBody: fmt.Sprintf( "body:%s%s%s%s", fmt.Sprintf( "/actionUrl/%%7BACTION_URL%%7D%s%s%s", "token_test", "name_test", "url_test", ), "token_test", "name_test", "url_test", ), }, } for i, s := range scenarios { subject, body, actionUrl := s.emailTemplate.Resolve("name_test", "url_test", "token_test") if s.expectedSubject != subject { t.Errorf("(%d) Expected subject %q got %q", i, s.expectedSubject, subject) } if s.expectedBody != body { t.Errorf("(%d) Expected body \n%v got \n%v", i, s.expectedBody, body) } if s.expectedActionUrl != actionUrl { t.Errorf("(%d) Expected actionUrl \n%v got \n%v", i, s.expectedActionUrl, actionUrl) } } } func TestLogsConfigValidate(t *testing.T) { scenarios := []struct { config settings.LogsConfig expectError bool }{ // zero values { settings.LogsConfig{}, false, }, // invalid data { settings.LogsConfig{MaxDays: -10}, true, }, // valid data { settings.LogsConfig{MaxDays: 1}, false, }, } for i, scenario := range scenarios { result := scenario.config.Validate() if result != nil && !scenario.expectError { t.Errorf("(%d) Didn't expect error, got %v", i, result) } if result == nil && scenario.expectError { t.Errorf("(%d) Expected error, got nil", i) } } } func TestAuthProviderConfigValidate(t *testing.T) { scenarios := []struct { config settings.AuthProviderConfig expectError bool }{ // zero values (disabled) { settings.AuthProviderConfig{}, false, }, // zero values (enabled) { settings.AuthProviderConfig{Enabled: true}, true, }, // invalid data { settings.AuthProviderConfig{ Enabled: true, ClientId: "", ClientSecret: "", AuthUrl: "test", TokenUrl: "test", UserApiUrl: "test", }, true, }, // valid data (only the required) { settings.AuthProviderConfig{ Enabled: true, ClientId: "test", ClientSecret: "test", }, false, }, // valid data (fill all fields) { settings.AuthProviderConfig{ Enabled: true, ClientId: "test", ClientSecret: "test", AuthUrl: "https://example.com", TokenUrl: "https://example.com", UserApiUrl: "https://example.com", }, false, }, } for i, scenario := range scenarios { result := scenario.config.Validate() if result != nil && !scenario.expectError { t.Errorf("(%d) Didn't expect error, got %v", i, result) } if result == nil && scenario.expectError { t.Errorf("(%d) Expected error, got nil", i) } } } func TestAuthProviderConfigSetupProvider(t *testing.T) { provider := auth.NewGithubProvider() // disabled config c1 := settings.AuthProviderConfig{Enabled: false} if err := c1.SetupProvider(provider); err == nil { t.Errorf("Expected error, got nil") } c2 := settings.AuthProviderConfig{ Enabled: true, ClientId: "test_ClientId", ClientSecret: "test_ClientSecret", AuthUrl: "test_AuthUrl", UserApiUrl: "test_UserApiUrl", TokenUrl: "test_TokenUrl", } if err := c2.SetupProvider(provider); err != nil { t.Error(err) } if provider.ClientId() != c2.ClientId { t.Fatalf("Expected ClientId %s, got %s", c2.ClientId, provider.ClientId()) } if provider.ClientSecret() != c2.ClientSecret { t.Fatalf("Expected ClientSecret %s, got %s", c2.ClientSecret, provider.ClientSecret()) } if provider.AuthUrl() != c2.AuthUrl { t.Fatalf("Expected AuthUrl %s, got %s", c2.AuthUrl, provider.AuthUrl()) } if provider.UserApiUrl() != c2.UserApiUrl { t.Fatalf("Expected UserApiUrl %s, got %s", c2.UserApiUrl, provider.UserApiUrl()) } if provider.TokenUrl() != c2.TokenUrl { t.Fatalf("Expected TokenUrl %s, got %s", c2.TokenUrl, provider.TokenUrl()) } }