first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:41:46 +03:00
commit b6e74bd024
56 changed files with 16114 additions and 0 deletions

View File

@@ -0,0 +1,652 @@
package controllers
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
"golang.org/x/oauth2"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"goaresv3/app/accounts/models"
"goaresv3/config"
jwtHelper "goaresv3/pkg/jwt"
)
func setupTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
if err != nil {
t.Fatalf("failed to open sqlite db: %v", err)
}
if err := db.AutoMigrate(&models.User{}); err != nil {
t.Fatalf("failed to migrate user model: %v", err)
}
if err := db.AutoMigrate(&models.SocialAccount{}); err != nil {
t.Fatalf("failed to migrate social account model: %v", err)
}
config.DB = db
return db
}
func boolPtr(v bool) *bool {
return &v
}
func TestVerifyEmailSuccess(t *testing.T) {
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
user := models.User{UserName: "u1", Email: "u1@example.com", EmailVerified: boolPtr(false), EmailVerifyToken: "tok-123"}
if err := db.Create(&user).Error; err != nil {
t.Fatalf("failed to create user: %v", err)
}
r := gin.New()
r.GET("/api/v1/auth/verify-email", VerifyEmail)
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/verify-email?token=tok-123", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
var updated models.User
if err := db.First(&updated, user.ID).Error; err != nil {
t.Fatalf("failed to fetch updated user: %v", err)
}
if !updated.IsEmailVerified() {
t.Fatal("expected email_verified=true")
}
if updated.EmailVerifyToken != "" {
t.Fatalf("expected email_verify_token cleared, got %q", updated.EmailVerifyToken)
}
}
func TestVerifyEmailMissingToken(t *testing.T) {
gin.SetMode(gin.TestMode)
setupTestDB(t)
r := gin.New()
r.GET("/api/v1/auth/verify-email", VerifyEmail)
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/verify-email", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", w.Code)
}
}
func TestVerifyEmailInvalidToken(t *testing.T) {
gin.SetMode(gin.TestMode)
setupTestDB(t)
r := gin.New()
r.GET("/api/v1/auth/verify-email", VerifyEmail)
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/verify-email?token=missing", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", w.Code)
}
}
func TestLoginRejectsUnverifiedUser(t *testing.T) {
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
t.Setenv("JWT_SECRET", "test-secret-1234567890")
t.Setenv("JWT_REFRESH_SECRET", "test-refresh-secret-1234567890")
hashed, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
user := models.User{UserName: "u2", Email: "u2@example.com", Password: string(hashed), EmailVerified: boolPtr(false)}
if err := db.Create(&user).Error; err != nil {
t.Fatalf("failed to create user: %v", err)
}
body, _ := json.Marshal(LoginRequest{Email: "u2@example.com", Password: "password123"})
r := gin.New()
r.POST("/api/v1/auth/login", Login)
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusForbidden {
t.Fatalf("expected 403, got %d", w.Code)
}
}
func TestLoginBadRequest(t *testing.T) {
gin.SetMode(gin.TestMode)
setupTestDB(t)
r := gin.New()
r.POST("/api/v1/auth/login", Login)
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewBufferString(`{"email":"bad"}`))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", w.Code)
}
}
func TestLoginInvalidCredentials(t *testing.T) {
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
t.Setenv("JWT_SECRET", "test-secret-1234567890")
t.Setenv("JWT_REFRESH_SECRET", "test-refresh-secret-1234567890")
hashed, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
user := models.User{UserName: "u2x", Email: "u2x@example.com", Password: string(hashed), EmailVerified: boolPtr(true)}
if err := db.Create(&user).Error; err != nil {
t.Fatalf("failed to create user: %v", err)
}
body, _ := json.Marshal(LoginRequest{Email: "u2x@example.com", Password: "wrong-pass"})
r := gin.New()
r.POST("/api/v1/auth/login", Login)
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Fatalf("expected 401, got %d", w.Code)
}
}
func TestLoginVerifiedUserSuccess(t *testing.T) {
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
t.Setenv("JWT_SECRET", "test-secret-1234567890")
t.Setenv("JWT_REFRESH_SECRET", "test-refresh-secret-1234567890")
hashed, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
user := models.User{UserName: "u3", Email: "u3@example.com", Password: string(hashed), EmailVerified: boolPtr(true)}
if err := db.Create(&user).Error; err != nil {
t.Fatalf("failed to create user: %v", err)
}
body, _ := json.Marshal(LoginRequest{Email: "u3@example.com", Password: "password123"})
r := gin.New()
r.POST("/api/v1/auth/login", Login)
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
}
func TestRefreshRejectsUnverifiedUser(t *testing.T) {
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
t.Setenv("JWT_SECRET", "test-secret-1234567890")
t.Setenv("JWT_REFRESH_SECRET", "test-refresh-secret-1234567890")
user := models.User{UserName: "u4", Email: "u4@example.com", EmailVerified: boolPtr(false)}
if err := db.Create(&user).Error; err != nil {
t.Fatalf("failed to create user: %v", err)
}
rt, err := jwtHelper.GenerateRefreshToken(user.ID, user.Email, user.UserName)
if err != nil {
t.Fatalf("failed to generate refresh token: %v", err)
}
body, _ := json.Marshal(RefreshRequest{RefreshToken: rt})
r := gin.New()
r.POST("/api/v1/auth/refresh", RefreshToken)
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/refresh", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusForbidden {
t.Fatalf("expected 403, got %d", w.Code)
}
}
func TestRefreshBadRequest(t *testing.T) {
gin.SetMode(gin.TestMode)
setupTestDB(t)
r := gin.New()
r.POST("/api/v1/auth/refresh", RefreshToken)
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/refresh", bytes.NewBufferString(`{"x":1}`))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", w.Code)
}
}
func TestRefreshInvalidToken(t *testing.T) {
gin.SetMode(gin.TestMode)
setupTestDB(t)
t.Setenv("JWT_REFRESH_SECRET", "test-refresh-secret-1234567890")
r := gin.New()
r.POST("/api/v1/auth/refresh", RefreshToken)
body := []byte(`{"refresh_token":"not-a-jwt"}`)
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/refresh", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Fatalf("expected 401, got %d", w.Code)
}
}
func TestRefreshInvalidUser(t *testing.T) {
gin.SetMode(gin.TestMode)
setupTestDB(t)
t.Setenv("JWT_REFRESH_SECRET", "test-refresh-secret-1234567890")
rt, err := jwtHelper.GenerateRefreshToken(9999, "ghost@example.com", "ghost")
if err != nil {
t.Fatalf("failed to generate refresh token: %v", err)
}
body, _ := json.Marshal(RefreshRequest{RefreshToken: rt})
r := gin.New()
r.POST("/api/v1/auth/refresh", RefreshToken)
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/refresh", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Fatalf("expected 401, got %d", w.Code)
}
}
func TestRegisterPasswordMismatch(t *testing.T) {
gin.SetMode(gin.TestMode)
setupTestDB(t)
r := gin.New()
r.POST("/api/v1/auth/register", Register)
body := []byte(`{"username":"u","email":"u@example.com","password":"password123","confirm_password":"different123"}`)
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/register", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", w.Code)
}
}
func TestRegisterDuplicateEmail(t *testing.T) {
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
verified := true
hashed, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
seed := models.User{UserName: "seed", Email: "dup@example.com", Password: string(hashed), EmailVerified: &verified}
if err := db.Create(&seed).Error; err != nil {
t.Fatalf("failed to seed user: %v", err)
}
r := gin.New()
r.POST("/api/v1/auth/register", Register)
body := []byte(`{"username":"newuser","email":"dup@example.com","password":"password123","confirm_password":"password123"}`)
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/register", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusConflict {
t.Fatalf("expected 409, got %d", w.Code)
}
}
func TestRegisterMailFailureRollsBackUser(t *testing.T) {
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
t.Setenv("EMAIL_HOST", "")
t.Setenv("EMAIL_PORT", "")
t.Setenv("EMAIL_FROM", "")
r := gin.New()
r.POST("/api/v1/auth/register", Register)
body := []byte(`{"username":"rollback","email":"rollback@example.com","password":"password123","confirm_password":"password123"}`)
req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/register", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusInternalServerError {
t.Fatalf("expected 500, got %d", w.Code)
}
var count int64
if err := db.Model(&models.User{}).Where("email = ?", "rollback@example.com").Count(&count).Error; err != nil {
t.Fatalf("failed to count users: %v", err)
}
if count != 0 {
t.Fatalf("expected rollback delete, user count=%d", count)
}
}
func TestMeIncludesUsernameFallbackFromDB(t *testing.T) {
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
user := models.User{UserName: "fallback-user", Email: "u5@example.com", EmailVerified: boolPtr(true)}
if err := db.Create(&user).Error; err != nil {
t.Fatalf("failed to create user: %v", err)
}
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest(http.MethodGet, "/api/v1/me", nil)
c.Set("user_id", user.ID)
c.Set("email", user.Email)
Me(c)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
var resp map[string]any
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if resp["username"] != "fallback-user" {
t.Fatalf("expected username=fallback-user, got %v", resp["username"])
}
}
func TestMeUsesContextUsernameWhenProvided(t *testing.T) {
gin.SetMode(gin.TestMode)
setupTestDB(t)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest(http.MethodGet, "/api/v1/me", nil)
c.Set("user_id", uint(99))
c.Set("email", "ctx@example.com")
c.Set("username", "ctx-user")
Me(c)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d", w.Code)
}
var resp map[string]any
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if resp["username"] != "ctx-user" {
t.Fatalf("expected username=ctx-user, got %v", resp["username"])
}
}
func TestGoogleLoginMissingConfig(t *testing.T) {
gin.SetMode(gin.TestMode)
setupTestDB(t)
t.Setenv("SOCIAL_AUTH_GOOGLE_OAUTH2_KEY", "")
t.Setenv("SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET", "")
t.Setenv("SOCIAL_AUTH_GOOGLE_REDIRECT_URL", "")
r := gin.New()
r.GET("/api/v1/auth/google/login", GoogleLogin)
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/google/login", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusServiceUnavailable {
t.Fatalf("expected 503, got %d", w.Code)
}
}
func TestGoogleCallbackInvalidState(t *testing.T) {
gin.SetMode(gin.TestMode)
setupTestDB(t)
t.Setenv("SOCIAL_AUTH_GOOGLE_OAUTH2_KEY", "client-id")
t.Setenv("SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET", "client-secret")
t.Setenv("SOCIAL_AUTH_GOOGLE_REDIRECT_URL", "http://localhost:8080/api/v1/auth/google/callback")
r := gin.New()
r.GET("/api/v1/auth/google/callback", GoogleCallback)
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/google/callback?state=state1&code=code1", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusBadRequest {
t.Fatalf("expected 400, got %d", w.Code)
}
}
func TestGoogleCallbackSuccessCreatesUserAndTokens(t *testing.T) {
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
t.Setenv("SOCIAL_AUTH_GOOGLE_OAUTH2_KEY", "client-id")
t.Setenv("SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET", "client-secret")
t.Setenv("SOCIAL_AUTH_GOOGLE_REDIRECT_URL", "http://localhost:8080/api/v1/auth/google/callback")
t.Setenv("JWT_SECRET", "test-secret-1234567890")
t.Setenv("JWT_REFRESH_SECRET", "test-refresh-secret-1234567890")
originalExchange := exchangeGoogleCode
originalFetch := fetchGoogleUserInfo
t.Cleanup(func() {
exchangeGoogleCode = originalExchange
fetchGoogleUserInfo = originalFetch
})
exchangeGoogleCode = func(ctx context.Context, cfg *oauth2.Config, code string) (*oauth2.Token, error) {
return &oauth2.Token{AccessToken: "google-access", TokenType: "Bearer"}, nil
}
fetchGoogleUserInfo = func(ctx context.Context, cfg *oauth2.Config, token *oauth2.Token) (*googleUserInfo, error) {
return &googleUserInfo{
Sub: "google-sub-123",
Email: "google.user@example.com",
EmailVerified: true,
Name: "Google User",
Picture: "https://cdn.example.com/avatar.png",
GivenName: "Google",
FamilyName: "User",
}, nil
}
r := gin.New()
r.GET("/api/v1/auth/google/callback", GoogleCallback)
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/google/callback?state=state123&code=code123", nil)
req.AddCookie(&http.Cookie{Name: googleOAuthStateCookieName, Value: "state123"})
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d body=%s", w.Code, w.Body.String())
}
var resp map[string]any
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if resp["access_token"] == "" || resp["refresh_token"] == "" {
t.Fatal("expected access_token and refresh_token in response")
}
if resp["provider"] != "google" {
t.Fatalf("expected provider=google, got %v", resp["provider"])
}
var user models.User
if err := db.Where("email = ?", "google.user@example.com").First(&user).Error; err != nil {
t.Fatalf("expected user to be created, err=%v", err)
}
if !user.IsEmailVerified() {
t.Fatal("expected created google user to be verified")
}
var social models.SocialAccount
if err := db.Where("user_id = ? AND provider = ? AND provider_id = ?", user.ID, "google", "google-sub-123").First(&social).Error; err != nil {
t.Fatalf("expected social account to be linked, err=%v", err)
}
}
func TestGitHubLoginMissingConfig(t *testing.T) {
gin.SetMode(gin.TestMode)
setupTestDB(t)
t.Setenv("SOCIAL_AUTH_GITHUB_KEY", "")
t.Setenv("SOCIAL_AUTH_GITHUB_SECRET", "")
t.Setenv("SOCIAL_AUTH_GITHUB_REDIRECT_URL", "")
r := gin.New()
r.GET("/api/v1/auth/github/login", GitHubLogin)
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/github/login", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusServiceUnavailable {
t.Fatalf("expected 503, got %d", w.Code)
}
}
func TestGitHubCallbackSuccessCreatesVerifiedUserAndTokens(t *testing.T) {
gin.SetMode(gin.TestMode)
db := setupTestDB(t)
t.Setenv("SOCIAL_AUTH_GITHUB_KEY", "gh-client-id")
t.Setenv("SOCIAL_AUTH_GITHUB_SECRET", "gh-client-secret")
t.Setenv("SOCIAL_AUTH_GITHUB_REDIRECT_URL", "http://localhost:8080/api/v1/auth/github/callback")
t.Setenv("JWT_SECRET", "test-secret-1234567890")
t.Setenv("JWT_REFRESH_SECRET", "test-refresh-secret-1234567890")
originalExchange := exchangeGitHubCode
originalFetch := fetchGitHubUserInfo
t.Cleanup(func() {
exchangeGitHubCode = originalExchange
fetchGitHubUserInfo = originalFetch
})
exchangeGitHubCode = func(ctx context.Context, cfg *oauth2.Config, code string) (*oauth2.Token, error) {
return &oauth2.Token{AccessToken: "github-access", TokenType: "Bearer"}, nil
}
fetchGitHubUserInfo = func(ctx context.Context, cfg *oauth2.Config, token *oauth2.Token) (*socialUserProfile, error) {
return &socialUserProfile{
ProviderID: "4242",
Email: "github.user@example.com",
Name: "GitHub User",
AvatarURL: "https://cdn.example.com/gh-avatar.png",
EmailVerified: true,
}, nil
}
r := gin.New()
r.GET("/api/v1/auth/github/callback", GitHubCallback)
req := httptest.NewRequest(http.MethodGet, "/api/v1/auth/github/callback?state=state-gh-123&code=code-gh-123", nil)
req.AddCookie(&http.Cookie{Name: githubOAuthStateCookieName, Value: "state-gh-123"})
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("expected 200, got %d body=%s", w.Code, w.Body.String())
}
var resp map[string]any
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
t.Fatalf("failed to decode response: %v", err)
}
if resp["access_token"] == "" || resp["refresh_token"] == "" {
t.Fatal("expected access_token and refresh_token in response")
}
if resp["provider"] != "github" {
t.Fatalf("expected provider=github, got %v", resp["provider"])
}
var user models.User
if err := db.Where("email = ?", "github.user@example.com").First(&user).Error; err != nil {
t.Fatalf("expected user to be created, err=%v", err)
}
if !user.IsEmailVerified() {
t.Fatal("expected created github user to be verified")
}
var social models.SocialAccount
if err := db.Where("user_id = ? AND provider = ? AND provider_id = ?", user.ID, "github", "4242").First(&social).Error; err != nil {
t.Fatalf("expected github social account to be linked, err=%v", err)
}
}
func TestGoogleOAuthConfigSupportsSocialAuthEnv(t *testing.T) {
t.Setenv("SOCIAL_AUTH_GOOGLE_OAUTH2_KEY", "'google-client-id-from-social'")
t.Setenv("SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET", "'google-client-secret-from-social'")
t.Setenv("SOCIAL_AUTH_GOOGLE_REDIRECT_URL", "")
t.Setenv("APP_BASE_URL", "http://localhost:8080")
cfg, err := getGoogleOAuthConfig()
if err != nil {
t.Fatalf("expected config, got error: %v", err)
}
if cfg.ClientID != "google-client-id-from-social" {
t.Fatalf("unexpected client id: %q", cfg.ClientID)
}
if cfg.ClientSecret != "google-client-secret-from-social" {
t.Fatalf("unexpected client secret: %q", cfg.ClientSecret)
}
if cfg.RedirectURL != "http://localhost:8080/api/v1/auth/google/callback" {
t.Fatalf("unexpected redirect url: %q", cfg.RedirectURL)
}
}
func TestGitHubOAuthConfigSupportsSocialAuthEnv(t *testing.T) {
t.Setenv("SOCIAL_AUTH_GITHUB_KEY", "'github-client-id-from-social'")
t.Setenv("SOCIAL_AUTH_GITHUB_SECRET", "'github-client-secret-from-social'")
t.Setenv("SOCIAL_AUTH_GITHUB_REDIRECT_URL", "")
t.Setenv("APP_BASE_URL", "http://localhost:8080")
cfg, err := getGitHubOAuthConfig()
if err != nil {
t.Fatalf("expected config, got error: %v", err)
}
if cfg.ClientID != "github-client-id-from-social" {
t.Fatalf("unexpected client id: %q", cfg.ClientID)
}
if cfg.ClientSecret != "github-client-secret-from-social" {
t.Fatalf("unexpected client secret: %q", cfg.ClientSecret)
}
if cfg.RedirectURL != "http://localhost:8080/api/v1/auth/github/callback" {
t.Fatalf("unexpected redirect url: %q", cfg.RedirectURL)
}
}