From 70ddc8c93c5dbed271f099b5e3685855bb9c289b Mon Sep 17 00:00:00 2001
From: hayzam <hayzam@gmail.com>
Date: Sat, 7 Sep 2024 19:09:27 +0530
Subject: [PATCH] test: auth: init tests

---
 services/auth.go          | 108 ++++++------
 services/auth_test.go     | 350 ++++++++++++++++++++++++++++++++++++++
 services/services_test.go |  74 +++++---
 utils/strings.go          |  12 ++
 utils/strings_test.go     |  34 ++++
 5 files changed, 503 insertions(+), 75 deletions(-)
 create mode 100644 services/auth_test.go

diff --git a/services/auth.go b/services/auth.go
index 5d1db7c..373ca24 100644
--- a/services/auth.go
+++ b/services/auth.go
@@ -18,6 +18,60 @@ func NewAuthService(db *gorm.DB) *AuthService {
 	return &AuthService{DB: db}
 }
 
+func (service *AuthService) GetUsers() ([]models.User, error) {
+	var users []models.User
+
+	if err := service.DB.Find(&users).Error; err != nil {
+		return nil, fmt.Errorf("failed_to_get_users")
+	}
+
+	return users, nil
+}
+
+func (service *AuthService) CreateJWT(username, password string) (map[string]interface{}, error) {
+	var user models.User
+
+	if err := service.DB.Where("username = ?", username).First(&user).Error; err != nil {
+		return nil, fmt.Errorf("user_not_found")
+	}
+
+	if !utils.CheckPasswordHash(password, user.Password) {
+		return nil, fmt.Errorf("invalid_password")
+	}
+
+	tokenString, expiry, err := utils.GenerateJWTAccessToken(user.ID, user.Username, user.Email, user.Photo, user.Admin, user.Permissions)
+	if err != nil {
+		return nil, fmt.Errorf("failed_to_generate_jwt")
+	}
+
+	newToken := models.Token{
+		UserID: user.ID,
+		Token:  tokenString,
+		Expiry: expiry,
+	}
+
+	if err := service.DB.Create(&newToken).Error; err != nil {
+		return nil, fmt.Errorf("failed_to_create_token")
+	}
+
+	claims, err := utils.ValidateJWT(tokenString)
+	if err != nil {
+		service.DB.Where("token = ?", tokenString).Delete(&models.Token{})
+		return nil, fmt.Errorf("invalid_jwt_created")
+	}
+
+	return map[string]interface{}{
+		"token":       tokenString,
+		"expiry":      claims.ExpiresAt.Time.String(),
+		"email":       claims.Email,
+		"username":    claims.Username,
+		"photo":       claims.Photo,
+		"userId":      claims.UserId,
+		"admin":       user.Admin,
+		"permissions": claims.Permissions,
+	}, nil
+}
+
 func (service *AuthService) VerifyTokenInDb(token string, needAdmin bool) bool {
 	var tokenRecord models.Token
 
@@ -191,16 +245,6 @@ func (service *AuthService) DeleteUser(username string) error {
 	return nil
 }
 
-func (service *AuthService) GetUsers() ([]models.User, error) {
-	var users []models.User
-
-	if err := service.DB.Find(&users).Error; err != nil {
-		return nil, fmt.Errorf("failed_to_get_users")
-	}
-
-	return users, nil
-}
-
 func (service *AuthService) GetUser(id uint) (models.User, error) {
 	var user models.User
 
@@ -211,50 +255,6 @@ func (service *AuthService) GetUser(id uint) (models.User, error) {
 	return user, nil
 }
 
-func (service *AuthService) CreateJWT(username, password string) (map[string]interface{}, error) {
-	var user models.User
-
-	if err := service.DB.Where("username = ?", username).First(&user).Error; err != nil {
-		return nil, fmt.Errorf("user_not_found")
-	}
-
-	if !utils.CheckPasswordHash(password, user.Password) {
-		return nil, fmt.Errorf("invalid_password")
-	}
-
-	tokenString, expiry, err := utils.GenerateJWTAccessToken(user.ID, user.Username, user.Email, user.Photo, user.Admin, user.Permissions)
-	if err != nil {
-		return nil, fmt.Errorf("failed_to_generate_jwt")
-	}
-
-	newToken := models.Token{
-		UserID: user.ID,
-		Token:  tokenString,
-		Expiry: expiry,
-	}
-
-	if err := service.DB.Create(&newToken).Error; err != nil {
-		return nil, fmt.Errorf("failed_to_create_token")
-	}
-
-	claims, err := utils.ValidateJWT(tokenString)
-	if err != nil {
-		service.DB.Where("token = ?", tokenString).Delete(&models.Token{})
-		return nil, fmt.Errorf("invalid_jwt_created")
-	}
-
-	return map[string]interface{}{
-		"token":       tokenString,
-		"expiry":      claims.ExpiresAt.Time.String(),
-		"email":       claims.Email,
-		"username":    claims.Username,
-		"photo":       claims.Photo,
-		"userId":      claims.UserId,
-		"admin":       user.Admin,
-		"permissions": claims.Permissions,
-	}, nil
-}
-
 func (service *AuthService) CreateJWTFromEmail(email string) (string, error) {
 	var user models.User
 
diff --git a/services/auth_test.go b/services/auth_test.go
new file mode 100644
index 0000000..71369df
--- /dev/null
+++ b/services/auth_test.go
@@ -0,0 +1,350 @@
+package services
+
+import (
+	"reflect"
+	"testing"
+	"time"
+
+	"git.difuse.io/Difuse/kalmia/db/models"
+	"git.difuse.io/Difuse/kalmia/logger"
+	"git.difuse.io/Difuse/kalmia/utils"
+	"go.uber.org/zap"
+)
+
+func TestGetUsers(t *testing.T) {
+	if TestAuthService == nil {
+		t.Fatal("TestAuthService is nil")
+	}
+
+	users, err := TestAuthService.GetUsers()
+	if err != nil {
+		t.Fatalf("GetUsers returned an error: %v", err)
+	}
+
+	expectedUsers := map[string]string{
+		"admin": "admin@kalmia.difuse.io",
+		"user":  "user@kalmia.difuse.io",
+	}
+
+	if len(users) != len(expectedUsers) {
+		t.Errorf("Expected %d users, but got %d", len(expectedUsers), len(users))
+	}
+
+	for _, user := range users {
+		expectedEmail, exists := expectedUsers[user.Username]
+		if !exists {
+			t.Errorf("Unexpected user found: %s", user.Username)
+			continue
+		}
+		if user.Email != expectedEmail {
+			t.Errorf("User %s has incorrect email. Expected %s, got %s", user.Username, expectedEmail, user.Email)
+		}
+		delete(expectedUsers, user.Username)
+	}
+
+	if len(expectedUsers) > 0 {
+		for username := range expectedUsers {
+			t.Errorf("Expected user not found: %s", username)
+		}
+	}
+}
+
+func TestCreateJWT(t *testing.T) {
+	if TestAuthService == nil {
+		t.Fatal("TestAuthService is nil")
+	}
+
+	t.Run("Successful JWT Creation", func(t *testing.T) {
+		result, err := TestAuthService.CreateJWT("admin", "admin")
+		if err != nil {
+			t.Fatalf("Failed to create JWT: %v", err)
+		}
+
+		// Check if all expected fields are present
+		expectedFields := []string{"token", "expiry", "email", "username", "photo", "userId", "admin", "permissions"}
+		for _, field := range expectedFields {
+			if _, ok := result[field]; !ok {
+				t.Errorf("Expected field %s is missing from the result", field)
+			}
+		}
+
+		// Verify some of the returned data
+		if result["username"] != "admin" {
+			t.Errorf("Expected username 'admin', got %v", result["username"])
+		}
+		if result["email"] != "admin@kalmia.difuse.io" {
+			t.Errorf("Expected email 'admin@kalmia.difuse.io', got %v", result["email"])
+		}
+		if result["admin"] != true {
+			t.Errorf("Expected admin to be true, got %v", result["admin"])
+		}
+
+		// Verify that the token was stored in the database
+		var storedToken models.Token
+		if err := TestAuthService.DB.Where("token = ?", result["token"]).First(&storedToken).Error; err != nil {
+			t.Errorf("Token not found in database: %v", err)
+		}
+	})
+
+	t.Run("Non-existent User", func(t *testing.T) {
+		_, err := TestAuthService.CreateJWT("nonexistent", "password")
+		if err == nil || err.Error() != "user_not_found" {
+			t.Errorf("Expected 'user_not_found' error, got %v", err)
+		}
+	})
+
+	t.Run("Incorrect Password", func(t *testing.T) {
+		_, err := TestAuthService.CreateJWT("admin", "wrongpassword")
+		if err == nil || err.Error() != "invalid_password" {
+			t.Errorf("Expected 'invalid_password' error, got %v", err)
+		}
+	})
+}
+
+func TestVerifyTokenInDb(t *testing.T) {
+	if TestAuthService == nil {
+		t.Fatal("TestAuthService is nil")
+	}
+
+	createToken := func(username string) (string, error) {
+		result, err := TestAuthService.CreateJWT(username, username)
+		if err != nil {
+			return "", err
+		}
+		return result["token"].(string), nil
+	}
+
+	t.Run("Valid Token - Non-Admin", func(t *testing.T) {
+		token, err := createToken("user")
+		if err != nil {
+			t.Fatalf("Failed to create token: %v", err)
+		}
+
+		isValid := TestAuthService.VerifyTokenInDb(token, false)
+		if !isValid {
+			t.Errorf("Expected token to be valid, but it was not")
+		}
+	})
+
+	t.Run("Valid Token - Admin Check for Non-Admin", func(t *testing.T) {
+		token, err := createToken("user")
+		if err != nil {
+			t.Fatalf("Failed to create token: %v", err)
+		}
+
+		isValid := TestAuthService.VerifyTokenInDb(token, true)
+		if isValid {
+			t.Errorf("Expected token to be invalid for admin check, but it was valid")
+		}
+	})
+
+	t.Run("Valid Token - Admin", func(t *testing.T) {
+		token, err := createToken("admin")
+		if err != nil {
+			t.Fatalf("Failed to create token: %v", err)
+		}
+
+		isValid := TestAuthService.VerifyTokenInDb(token, true)
+		if !isValid {
+			t.Errorf("Expected admin token to be valid, but it was not")
+		}
+	})
+
+	t.Run("Invalid Token", func(t *testing.T) {
+		isValid := TestAuthService.VerifyTokenInDb("invalid_token", false)
+		if isValid {
+			t.Errorf("Expected invalid token to be rejected, but it was accepted")
+		}
+	})
+
+	t.Run("Expired Token", func(t *testing.T) {
+		token, err := createToken("user")
+		if err != nil {
+			t.Fatalf("Failed to create token: %v", err)
+		}
+
+		err = TestAuthService.DB.Model(&models.Token{}).Where("token = ?", token).Update("expiry", time.Now().Add(-1*time.Hour)).Error
+		if err != nil {
+			t.Fatalf("Failed to expire token: %v", err)
+		}
+
+		isValid := TestAuthService.VerifyTokenInDb(token, false)
+		if isValid {
+			t.Errorf("Expected expired token to be invalid, but it was valid")
+		}
+	})
+}
+
+func createToken(username, password string) (string, error) {
+	result, err := TestAuthService.CreateJWT(username, password)
+	if err != nil {
+		logger.Error("Failed to create JWT", zap.Error(err))
+		return "", err
+	}
+	return result["token"].(string), nil
+}
+
+func TestIsTokenAdmin(t *testing.T) {
+	if TestAuthService == nil {
+		t.Fatal("TestAuthService is nil")
+	}
+
+	t.Run("Admin Token", func(t *testing.T) {
+		adminToken, err := createToken("admin", "admin")
+		if err != nil {
+			t.Fatalf("Failed to create admin token: %v", err)
+		}
+
+		isAdmin := TestAuthService.IsTokenAdmin(adminToken)
+		if !isAdmin {
+			t.Errorf("Expected admin token to be identified as admin, but it was not")
+		}
+	})
+
+	t.Run("Non-Admin Token", func(t *testing.T) {
+		userToken, err := createToken("user", "user")
+		if err != nil {
+			t.Fatalf("Failed to create user token: %v", err)
+		}
+
+		isAdmin := TestAuthService.IsTokenAdmin(userToken)
+		if isAdmin {
+			t.Errorf("Expected non-admin token to be identified as non-admin, but it was identified as admin")
+		}
+	})
+
+	t.Run("Invalid Token", func(t *testing.T) {
+		isAdmin := TestAuthService.IsTokenAdmin("invalid_token")
+		if isAdmin {
+			t.Errorf("Expected invalid token to be identified as non-admin, but it was identified as admin")
+		}
+	})
+
+	t.Run("Deleted User Token", func(t *testing.T) {
+		userName := "testuser" + time.Now().String()
+		password := "testpassword"
+		pwHash, err := utils.HashPassword(password)
+		if err != nil {
+			t.Fatalf("Failed to hash test password: %v", err)
+		}
+		testUser := models.User{
+			Username:    userName,
+			Email:       "testuser@example.com",
+			Password:    pwHash,
+			Admin:       false,
+			Permissions: "[\"read\",\"write\",\"delete\"]",
+		}
+		if err := TestAuthService.DB.Create(&testUser).Error; err != nil {
+			t.Fatalf("Failed to create test user: %v", err)
+		}
+
+		userToken, err := createToken(userName, password)
+		if err != nil {
+			t.Fatalf("Failed to create user token: %v", err)
+		}
+
+		if err := TestAuthService.DB.Where("user_id = ?", testUser.ID).Delete(&models.Token{}).Error; err != nil {
+			t.Fatalf("Failed to delete associated tokens: %v", err)
+		}
+
+		if err := TestAuthService.DB.Unscoped().Delete(&testUser).Error; err != nil {
+			t.Fatalf("Failed to delete test user: %v", err)
+		}
+
+		isAdmin := TestAuthService.IsTokenAdmin(userToken)
+		if isAdmin {
+			t.Errorf("Expected token for deleted user to be identified as non-admin, but it was identified as admin")
+		}
+	})
+}
+
+func TestGetUserPermissions(t *testing.T) {
+	if TestAuthService == nil {
+		t.Fatal("TestAuthService is nil")
+	}
+
+	t.Run("Admin Permissions", func(t *testing.T) {
+		adminToken, err := createToken("admin", "admin")
+		if err != nil {
+			t.Fatalf("Failed to create admin token: %v", err)
+		}
+
+		permissions, err := TestAuthService.GetUserPermissions(adminToken)
+		if err != nil {
+			t.Fatalf("Failed to get admin permissions: %v", err)
+		}
+
+		if len(permissions) != 1 || permissions[0] != "all" {
+			t.Errorf("Expected admin permissions to be [\"all\"], got %v", permissions)
+		}
+	})
+
+	t.Run("Regular User Permissions", func(t *testing.T) {
+		userToken, err := createToken("user", "user")
+		if err != nil {
+			t.Fatalf("Failed to create user token: %v", err)
+		}
+
+		permissions, err := TestAuthService.GetUserPermissions(userToken)
+		if err != nil {
+			t.Fatalf("Failed to get user permissions: %v", err)
+		}
+
+		expectedPermissions := []string{"read", "write", "delete"}
+		if !reflect.DeepEqual(permissions, expectedPermissions) {
+			t.Errorf("Expected user permissions to be %v, got %v", expectedPermissions, permissions)
+		}
+	})
+
+	t.Run("Custom User with Read Permission", func(t *testing.T) {
+		userName := "readonlyuser" + time.Now().String()
+		password := "testpassword"
+		pwHash, err := utils.HashPassword(password)
+		if err != nil {
+			t.Fatalf("Failed to hash test password: %v", err)
+		}
+
+		testUser := models.User{
+			Username:    userName,
+			Email:       "readonly@example.com",
+			Password:    pwHash,
+			Admin:       false,
+			Permissions: "[\"read\"]",
+		}
+		if err := TestAuthService.DB.Create(&testUser).Error; err != nil {
+			t.Fatalf("Failed to create test user: %v", err)
+		}
+		defer func() {
+			// Delete associated tokens first
+			if err := TestAuthService.DB.Where("user_id = ?", testUser.ID).Delete(&models.Token{}).Error; err != nil {
+				t.Fatalf("Failed to delete associated tokens: %v", err)
+			}
+			// Then delete the user
+			if err := TestAuthService.DB.Unscoped().Delete(&testUser).Error; err != nil {
+				t.Fatalf("Failed to delete test user: %v", err)
+			}
+		}()
+
+		userToken, err := createToken(userName, password)
+		if err != nil {
+			t.Fatalf("Failed to create user token: %v", err)
+		}
+
+		permissions, err := TestAuthService.GetUserPermissions(userToken)
+		if err != nil {
+			t.Fatalf("Failed to get user permissions: %v", err)
+		}
+
+		expectedPermissions := []string{"read"}
+		if !reflect.DeepEqual(permissions, expectedPermissions) {
+			t.Errorf("Expected user permissions to be %v, got %v", expectedPermissions, permissions)
+		}
+	})
+
+	t.Run("Invalid Token", func(t *testing.T) {
+		_, err := TestAuthService.GetUserPermissions("invalid_token")
+		if err == nil {
+			t.Error("Expected error for invalid token, got nil")
+		}
+	})
+}
diff --git a/services/services_test.go b/services/services_test.go
index e1ef212..8c1aefd 100644
--- a/services/services_test.go
+++ b/services/services_test.go
@@ -5,39 +5,71 @@ import (
 	"testing"
 
 	"git.difuse.io/Difuse/kalmia/config"
+	"git.difuse.io/Difuse/kalmia/db"
 	"git.difuse.io/Difuse/kalmia/logger"
+	"git.difuse.io/Difuse/kalmia/utils"
+	"go.uber.org/zap"
 )
 
 var TestConfig *config.Config
+var TestAuthService *AuthService
+var TestDocService *DocService
 
 func TestMain(m *testing.M) {
-	TestConfig = config.ParseConfig("../config.json")
-
-	TestConfig.Environment = "debug"
-	TestConfig.Port = 3737
-	TestConfig.LogLevel = "debug"
-	TestConfig.Database = "sqlite"
-	TestConfig.SessionSecret = "test-secret"
-
-	adminUser := config.User{
-		Username: "admin",
-		Email:    "admin@kalmia.difuse.io",
-		Password: "admin",
-		Admin:    true,
+	configJson := `{
+		"environment": "debug",
+		"port": 3737,
+		"logLevel": "debug",
+		"database": "sqlite",
+		"sessionSecret": "test",
+		"dataPath": "./service_test_dir",
+		"users": [{"username": "admin", "email": "admin@kalmia.difuse.io", "password": "admin", "admin": true}, 
+				  {"username": "user", "email": "user@kalmia.difuse.io", "password": "user", "admin": false}]
+	}`
+
+	err := utils.TouchFile("./config.json")
+
+	if err != nil {
+		panic(err)
+	}
+
+	prettyJson, err := utils.PrettyJSON(configJson)
+
+	if err != nil {
+		prettyJson = configJson
 	}
 
-	nonAdminUser := config.User{
-		Username: "user",
-		Email:    "user@kalmia.difuse.io",
-		Password: "user",
-		Admin:    false,
+	err = utils.WriteToFile("./config.json", prettyJson)
+
+	if err != nil {
+		panic(err)
 	}
 
-	TestConfig.Admins = append(TestConfig.Admins, adminUser, nonAdminUser)
+	TestConfig = config.ParseConfig("./config.json")
+
+	logger.InitializeLogger("test", TestConfig.LogLevel, TestConfig.DataPath)
+
+	d := db.SetupDatabase(TestConfig.Environment, TestConfig.Database, TestConfig.DataPath)
+	db.SetupBasicData(d, TestConfig.Admins)
+	db.InitCache()
+
+	serviceRegistry := NewServiceRegistry(d)
+	TestAuthService = serviceRegistry.AuthService
+	TestDocService = serviceRegistry.DocService
 
-	logger.InitializeLogger("test", "debug", "./service_test_dir")
 	code := m.Run()
 
-	os.RemoveAll("./service_test_dir")
+	err = utils.RemovePath(TestConfig.DataPath)
+
+	if err != nil {
+		logger.Error("Failed to remove test data path", zap.Error(err))
+	}
+
+	err = utils.RemovePath("./config.json")
+
+	if err != nil {
+		logger.Error("Failed to remove test config file: %v", zap.Error(err))
+	}
+
 	os.Exit(code)
 }
diff --git a/utils/strings.go b/utils/strings.go
index 7b7807d..cd5d83c 100644
--- a/utils/strings.go
+++ b/utils/strings.go
@@ -1,9 +1,12 @@
 package utils
 
 import (
+	"bytes"
 	"crypto/sha256"
 	"encoding/base64"
 	"encoding/hex"
+	"encoding/json"
+	"fmt"
 	"net/url"
 	"path/filepath"
 	"regexp"
@@ -147,3 +150,12 @@ func HashStrings(data []string) string {
 	h.Write([]byte(strings.Join(data, "")))
 	return hex.EncodeToString(h.Sum(nil))
 }
+
+func PrettyJSON(input string) (string, error) {
+	var prettyJSON bytes.Buffer
+	err := json.Indent(&prettyJSON, []byte(input), "", "  ")
+	if err != nil {
+		return "", fmt.Errorf("error formatting JSON: %v", err)
+	}
+	return prettyJSON.String(), nil
+}
diff --git a/utils/strings_test.go b/utils/strings_test.go
index a564a92..4a1b610 100644
--- a/utils/strings_test.go
+++ b/utils/strings_test.go
@@ -647,3 +647,37 @@ func TestHashStrings(t *testing.T) {
 		}
 	}
 }
+
+func TestPrettyJSON(t *testing.T) {
+	tests := []struct {
+		input    string
+		expected string
+		hasError bool
+	}{
+		{
+			input: `{"name":"John","age":30,"city":"New York"}`,
+			expected: `{
+  "name": "John",
+  "age": 30,
+  "city": "New York"
+}`,
+			hasError: false,
+		},
+		{
+			input:    `{"name":"John","age":30,"city":"New York"`, // Invalid JSON
+			expected: "",
+			hasError: true,
+		},
+	}
+
+	for _, tt := range tests {
+		pretty, err := PrettyJSON(tt.input)
+		if (err != nil) != tt.hasError {
+			t.Fatalf("Expected error: %v, got: %v", tt.hasError, err)
+		}
+
+		if pretty != tt.expected && !tt.hasError {
+			t.Fatalf("Expected:\n%s\nGot:\n%s", tt.expected, pretty)
+		}
+	}
+}