mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-10-18 20:18:15 +02:00
feat: Update dependencies and enhance job run details template
- Add new dependencies: `github.com/joho/godotenv`, `github.com/stretchr/testify`, and `gopkg.in/natefinch/lumberjack.v2` - Remove indirect dependency on `github.com/joho/godotenv` - Refactor JobRunDetails template to separate content rendering for improved testing - Enhance error message display in JobRunDetails template - Introduce new test files for JWT and password functionalities - Add comprehensive tests for database operations and error handling
This commit is contained in:
1 parent
a8b4588ecb
commit
3193bf5111
33 files changed
+9185
-232
No files matched your search
@@ -0,0 +1,704 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// setupTestDB creates an in-memory SQLite database for testing
|
||||
func setupTestDB(t *testing.T) *DB {
|
||||
gormDB, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open in-memory database: %v", err)
|
||||
}
|
||||
|
||||
// Initialize the database schema
|
||||
err = gormDB.AutoMigrate(
|
||||
&User{},
|
||||
&PasswordHistory{},
|
||||
&PasswordResetToken{},
|
||||
&TransferConfig{},
|
||||
&Job{},
|
||||
&JobHistory{},
|
||||
&FileMetadata{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to migrate database: %v", err)
|
||||
}
|
||||
|
||||
return &DB{DB: gormDB}
|
||||
}
|
||||
|
||||
func TestUserCRUD(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a test user
|
||||
testUser := &User{
|
||||
Email: fmt.Sprintf("test-%d@example.com", time.Now().UnixNano()),
|
||||
PasswordHash: "hashed_password",
|
||||
IsAdmin: true,
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
|
||||
// Test Create
|
||||
err := db.CreateUser(testUser)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create user: %v", err)
|
||||
}
|
||||
assert.NotZero(t, testUser.ID, "User ID should be set after creation")
|
||||
|
||||
// Test Read
|
||||
retrievedUser, err := db.GetUserByEmail(testUser.Email)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get user by email: %v", err)
|
||||
}
|
||||
assert.Equal(t, testUser.ID, retrievedUser.ID, "Retrieved user should have the same ID")
|
||||
assert.Equal(t, testUser.Email, retrievedUser.Email, "Retrieved user should have the same email")
|
||||
assert.Equal(t, testUser.PasswordHash, retrievedUser.PasswordHash, "Retrieved user should have the same password hash")
|
||||
assert.Equal(t, testUser.IsAdmin, retrievedUser.IsAdmin, "Retrieved user should have the same admin status")
|
||||
|
||||
// Test Update
|
||||
retrievedUser.Email = fmt.Sprintf("updated-%d@example.com", time.Now().UnixNano())
|
||||
err = db.UpdateUser(retrievedUser)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to update user: %v", err)
|
||||
}
|
||||
|
||||
// Verify update
|
||||
updatedUser, err := db.GetUserByID(retrievedUser.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get user by ID: %v", err)
|
||||
}
|
||||
assert.Equal(t, retrievedUser.Email, updatedUser.Email, "User email should be updated")
|
||||
}
|
||||
|
||||
func TestPasswordResetToken(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a test user
|
||||
testUser := &User{
|
||||
Email: fmt.Sprintf("test-%d@example.com", time.Now().UnixNano()),
|
||||
PasswordHash: "hashed_password",
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
err := db.CreateUser(testUser)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create user: %v", err)
|
||||
}
|
||||
|
||||
// Create a password reset token
|
||||
tokenString := fmt.Sprintf("test-token-%d", time.Now().UnixNano())
|
||||
expiresAt := time.Now().Add(24 * time.Hour)
|
||||
testToken := &PasswordResetToken{
|
||||
UserID: testUser.ID,
|
||||
Token: tokenString,
|
||||
ExpiresAt: expiresAt,
|
||||
}
|
||||
err = db.CreatePasswordResetToken(testToken)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create password reset token: %v", err)
|
||||
}
|
||||
assert.NotZero(t, testToken.ID, "Token ID should be set after creation")
|
||||
|
||||
// Retrieve the token
|
||||
retrievedToken, err := db.GetPasswordResetToken(tokenString)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get password reset token: %v", err)
|
||||
}
|
||||
assert.Equal(t, testToken.ID, retrievedToken.ID, "Retrieved token should have the same ID")
|
||||
assert.Equal(t, testUser.ID, retrievedToken.UserID, "Retrieved token should reference the correct user")
|
||||
assert.False(t, retrievedToken.Used, "Token should not be marked as used initially")
|
||||
|
||||
// Mark token as used
|
||||
err = db.MarkPasswordResetTokenAsUsed(retrievedToken.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to mark token as used: %v", err)
|
||||
}
|
||||
|
||||
// Verify token is marked as used
|
||||
// Note: We need to use GetPasswordResetTokenByID instead of GetPasswordResetToken
|
||||
// because GetPasswordResetToken filters out used tokens
|
||||
var updatedToken PasswordResetToken
|
||||
result := db.DB.First(&updatedToken, retrievedToken.ID)
|
||||
if result.Error != nil {
|
||||
t.Fatalf("Failed to get updated password reset token: %v", result.Error)
|
||||
}
|
||||
assert.True(t, updatedToken.Used, "Token should be marked as used")
|
||||
}
|
||||
|
||||
func TestTransferConfigCRUD(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a test user first
|
||||
testUser := &User{
|
||||
Email: fmt.Sprintf("test-%d@example.com", time.Now().UnixNano()),
|
||||
PasswordHash: "hashed_password",
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
err := db.CreateUser(testUser)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create user: %v", err)
|
||||
}
|
||||
|
||||
// Create a test transfer config
|
||||
testConfig := &TransferConfig{
|
||||
Name: fmt.Sprintf("Test Transfer %d", time.Now().UnixNano()),
|
||||
SourceType: "local",
|
||||
SourcePath: "/source/path",
|
||||
DestinationType: "local",
|
||||
DestinationPath: "/destination/path",
|
||||
FilePattern: "*.txt",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
|
||||
// Test Create
|
||||
err = db.CreateTransferConfig(testConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create transfer config: %v", err)
|
||||
}
|
||||
assert.NotZero(t, testConfig.ID, "Config ID should be set after creation")
|
||||
|
||||
// Test Read
|
||||
retrievedConfig, err := db.GetTransferConfig(testConfig.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get transfer config: %v", err)
|
||||
}
|
||||
assert.Equal(t, testConfig.Name, retrievedConfig.Name, "Retrieved config should have the same name")
|
||||
assert.Equal(t, testConfig.SourcePath, retrievedConfig.SourcePath, "Retrieved config should have the same source path")
|
||||
|
||||
// Test Update
|
||||
retrievedConfig.Name = fmt.Sprintf("Updated Transfer %d", time.Now().UnixNano())
|
||||
err = db.UpdateTransferConfig(retrievedConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to update transfer config: %v", err)
|
||||
}
|
||||
|
||||
// Verify update
|
||||
updatedConfig, err := db.GetTransferConfig(retrievedConfig.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get updated transfer config: %v", err)
|
||||
}
|
||||
assert.Equal(t, retrievedConfig.Name, updatedConfig.Name, "Config name should be updated")
|
||||
|
||||
// Test listing configs
|
||||
configs, err := db.GetTransferConfigs(testUser.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to list transfer configs: %v", err)
|
||||
}
|
||||
assert.GreaterOrEqual(t, len(configs), 1, "There should be at least one config in the list")
|
||||
|
||||
// Test Delete
|
||||
err = db.DeleteTransferConfig(testConfig.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to delete transfer config: %v", err)
|
||||
}
|
||||
|
||||
// Verify deletion
|
||||
_, err = db.GetTransferConfig(testConfig.ID)
|
||||
assert.Error(t, err, "Getting deleted config should return an error")
|
||||
}
|
||||
|
||||
func TestJobCRUD(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a test user first
|
||||
testUser := &User{
|
||||
Email: fmt.Sprintf("test-%d@example.com", time.Now().UnixNano()),
|
||||
PasswordHash: "hashed_password",
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
err := db.CreateUser(testUser)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create user: %v", err)
|
||||
}
|
||||
|
||||
// Create a test transfer config
|
||||
testConfig := &TransferConfig{
|
||||
Name: fmt.Sprintf("Test Transfer %d", time.Now().UnixNano()),
|
||||
SourceType: "local",
|
||||
SourcePath: "/source/path",
|
||||
DestinationType: "local",
|
||||
DestinationPath: "/destination/path",
|
||||
FilePattern: "*.txt",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateTransferConfig(testConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create transfer config: %v", err)
|
||||
}
|
||||
|
||||
// Create a test job
|
||||
now := time.Now()
|
||||
nextRun := now.Add(24 * time.Hour)
|
||||
testJob := &Job{
|
||||
Name: fmt.Sprintf("Test Job %d", time.Now().UnixNano()),
|
||||
ConfigID: testConfig.ID,
|
||||
Schedule: "0 * * * *", // Run every hour
|
||||
Enabled: true,
|
||||
LastRun: &now,
|
||||
NextRun: &nextRun,
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
|
||||
// Test Create
|
||||
err = db.CreateJob(testJob)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create job: %v", err)
|
||||
}
|
||||
assert.NotZero(t, testJob.ID, "Job ID should be set after creation")
|
||||
|
||||
// Test Read
|
||||
retrievedJob, err := db.GetJob(testJob.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get job: %v", err)
|
||||
}
|
||||
assert.Equal(t, testJob.Name, retrievedJob.Name, "Retrieved job should have the same name")
|
||||
assert.Equal(t, testJob.ConfigID, retrievedJob.ConfigID, "Retrieved job should have the same config ID")
|
||||
assert.Equal(t, testJob.Schedule, retrievedJob.Schedule, "Retrieved job should have the same schedule")
|
||||
|
||||
// Test listing jobs
|
||||
jobs, err := db.GetJobs(testUser.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to list jobs: %v", err)
|
||||
}
|
||||
assert.GreaterOrEqual(t, len(jobs), 1, "There should be at least one job in the list")
|
||||
|
||||
// Test Get Active Jobs
|
||||
activeJobs, err := db.GetActiveJobs()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get active jobs: %v", err)
|
||||
}
|
||||
assert.GreaterOrEqual(t, len(activeJobs), 1, "There should be at least one active job")
|
||||
|
||||
// Test Update
|
||||
retrievedJob.Name = fmt.Sprintf("Updated Job %d", time.Now().UnixNano())
|
||||
retrievedJob.Enabled = false
|
||||
err = db.UpdateJob(retrievedJob)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to update job: %v", err)
|
||||
}
|
||||
|
||||
// Verify update
|
||||
updatedJob, err := db.GetJob(retrievedJob.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get updated job: %v", err)
|
||||
}
|
||||
assert.Equal(t, retrievedJob.Name, updatedJob.Name, "Job name should be updated")
|
||||
assert.Equal(t, retrievedJob.Enabled, updatedJob.Enabled, "Job enabled status should be updated")
|
||||
|
||||
// Test Delete
|
||||
err = db.DeleteJob(testJob.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to delete job: %v", err)
|
||||
}
|
||||
|
||||
// Verify deletion
|
||||
_, err = db.GetJob(testJob.ID)
|
||||
assert.Error(t, err, "Getting deleted job should return an error")
|
||||
}
|
||||
|
||||
func TestJobHistoryCRUD(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a test user first
|
||||
testUser := &User{
|
||||
Email: fmt.Sprintf("test-%d@example.com", time.Now().UnixNano()),
|
||||
PasswordHash: "hashed_password",
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
err := db.CreateUser(testUser)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create user: %v", err)
|
||||
}
|
||||
|
||||
// Create a test transfer config
|
||||
testConfig := &TransferConfig{
|
||||
Name: fmt.Sprintf("Test Transfer %d", time.Now().UnixNano()),
|
||||
SourceType: "local",
|
||||
SourcePath: "/source/path",
|
||||
DestinationType: "local",
|
||||
DestinationPath: "/destination/path",
|
||||
FilePattern: "*.txt",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateTransferConfig(testConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create transfer config: %v", err)
|
||||
}
|
||||
|
||||
// Create a test job
|
||||
testJob := &Job{
|
||||
Name: fmt.Sprintf("Test Job %d", time.Now().UnixNano()),
|
||||
ConfigID: testConfig.ID,
|
||||
Schedule: "0 * * * *", // Run every hour
|
||||
Enabled: true,
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateJob(testJob)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create job: %v", err)
|
||||
}
|
||||
|
||||
// Create a test job history record
|
||||
startTime := time.Now().Add(-1 * time.Hour)
|
||||
endTime := time.Now()
|
||||
testHistory := &JobHistory{
|
||||
JobID: testJob.ID,
|
||||
StartTime: startTime,
|
||||
EndTime: &endTime,
|
||||
Status: "completed",
|
||||
BytesTransferred: 1024,
|
||||
FilesTransferred: 5,
|
||||
ErrorMessage: "",
|
||||
}
|
||||
|
||||
// Test Create
|
||||
err = db.CreateJobHistory(testHistory)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create job history: %v", err)
|
||||
}
|
||||
assert.NotZero(t, testHistory.ID, "Job history ID should be set after creation")
|
||||
|
||||
// Test Update
|
||||
testHistory.Status = "failed"
|
||||
testHistory.ErrorMessage = "Test error message"
|
||||
err = db.UpdateJobHistory(testHistory)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to update job history: %v", err)
|
||||
}
|
||||
|
||||
// Test getting job history
|
||||
histories, err := db.GetJobHistory(testJob.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get job history: %v", err)
|
||||
}
|
||||
assert.Equal(t, 1, len(histories), "There should be one job history record")
|
||||
assert.Equal(t, "failed", histories[0].Status, "Job history status should be 'failed'")
|
||||
assert.Equal(t, "Test error message", histories[0].ErrorMessage, "Job history error message should be set")
|
||||
}
|
||||
|
||||
func TestFileMetadataCRUD(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a test user first
|
||||
testUser := &User{
|
||||
Email: fmt.Sprintf("test-%d@example.com", time.Now().UnixNano()),
|
||||
PasswordHash: "hashed_password",
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
err := db.CreateUser(testUser)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create user: %v", err)
|
||||
}
|
||||
|
||||
// Create a test transfer config
|
||||
testConfig := &TransferConfig{
|
||||
Name: fmt.Sprintf("Test Transfer %d", time.Now().UnixNano()),
|
||||
SourceType: "local",
|
||||
SourcePath: "/source/path",
|
||||
DestinationType: "local",
|
||||
DestinationPath: "/destination/path",
|
||||
FilePattern: "*.txt",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateTransferConfig(testConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create transfer config: %v", err)
|
||||
}
|
||||
|
||||
// Create a test job
|
||||
testJob := &Job{
|
||||
Name: fmt.Sprintf("Test Job %d", time.Now().UnixNano()),
|
||||
ConfigID: testConfig.ID,
|
||||
Schedule: "0 * * * *", // Run every hour
|
||||
Enabled: true,
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateJob(testJob)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create job: %v", err)
|
||||
}
|
||||
|
||||
// Create a test file metadata record
|
||||
fileName := fmt.Sprintf("testfile-%d.txt", time.Now().UnixNano())
|
||||
fileHash := fmt.Sprintf("md5-%d", time.Now().UnixNano())
|
||||
testMetadata := &FileMetadata{
|
||||
JobID: testJob.ID,
|
||||
FileName: fileName,
|
||||
OriginalPath: "/source/path/" + fileName,
|
||||
FileSize: 1024,
|
||||
FileHash: fileHash,
|
||||
CreationTime: time.Now().Add(-2 * time.Hour),
|
||||
ModTime: time.Now().Add(-1 * time.Hour),
|
||||
ProcessedTime: time.Now(),
|
||||
DestinationPath: "/destination/path/" + fileName,
|
||||
Status: "processed",
|
||||
ErrorMessage: "",
|
||||
}
|
||||
|
||||
// Test Create
|
||||
err = db.CreateFileMetadata(testMetadata)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create file metadata: %v", err)
|
||||
}
|
||||
assert.NotZero(t, testMetadata.ID, "File metadata ID should be set after creation")
|
||||
|
||||
// Test GetFileMetadataByJobAndName
|
||||
retrievedMetadata, err := db.GetFileMetadataByJobAndName(testJob.ID, fileName)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get file metadata by job and name: %v", err)
|
||||
}
|
||||
assert.Equal(t, testMetadata.ID, retrievedMetadata.ID, "Retrieved metadata should have the same ID")
|
||||
assert.Equal(t, fileName, retrievedMetadata.FileName, "Retrieved metadata should have the same file name")
|
||||
assert.Equal(t, fileHash, retrievedMetadata.FileHash, "Retrieved metadata should have the same file hash")
|
||||
|
||||
// Test GetFileMetadataByHash
|
||||
hashMetadata, err := db.GetFileMetadataByHash(fileHash)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get file metadata by hash: %v", err)
|
||||
}
|
||||
assert.Equal(t, testMetadata.ID, hashMetadata.ID, "Retrieved metadata should have the same ID")
|
||||
|
||||
// Test Delete
|
||||
err = db.DeleteFileMetadata(testMetadata.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to delete file metadata: %v", err)
|
||||
}
|
||||
|
||||
// Verify deletion
|
||||
_, err = db.GetFileMetadataByJobAndName(testJob.ID, fileName)
|
||||
assert.Error(t, err, "Getting deleted file metadata should return an error")
|
||||
}
|
||||
|
||||
func TestDBInitialize(t *testing.T) {
|
||||
// Create a temporary file path for testing
|
||||
tempDBPath := "test_init.db"
|
||||
|
||||
// Initialize the database
|
||||
db, err := Initialize(tempDBPath)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, db)
|
||||
|
||||
// Cleanup
|
||||
err = db.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Remove test file
|
||||
err = os.Remove(tempDBPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
t.Logf("Warning: could not remove test database file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConfigRclonePath(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a test user
|
||||
testUser := &User{
|
||||
Email: "rclone-test@example.com",
|
||||
PasswordHash: "hashed_password",
|
||||
IsAdmin: false,
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
|
||||
err := db.CreateUser(testUser)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a test config
|
||||
testConfig := &TransferConfig{
|
||||
Name: "Test Rclone Config",
|
||||
SourceType: "local",
|
||||
SourcePath: "/source/path",
|
||||
DestinationType: "sftp",
|
||||
DestHost: "example.com",
|
||||
DestPort: 22,
|
||||
DestUser: "testuser",
|
||||
DestinationPath: "/remote/path",
|
||||
DestKeyFile: "private_key_content",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
|
||||
err = db.CreateTransferConfig(testConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test GetConfigRclonePath
|
||||
configPath := db.GetConfigRclonePath(testConfig)
|
||||
assert.NotEmpty(t, configPath)
|
||||
assert.Contains(t, configPath, fmt.Sprintf("%d", testConfig.ID))
|
||||
}
|
||||
|
||||
func TestGenerateRcloneConfig(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a test user
|
||||
testUser := &User{
|
||||
Email: "rclone-gen-test@example.com",
|
||||
PasswordHash: "hashed_password",
|
||||
IsAdmin: false,
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
|
||||
err := db.CreateUser(testUser)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// SFTP config test
|
||||
sftpConfig := &TransferConfig{
|
||||
Name: "Test SFTP Config",
|
||||
SourceType: "local",
|
||||
SourcePath: "/local/path",
|
||||
DestinationType: "sftp",
|
||||
DestHost: "sftp.example.com",
|
||||
DestPort: 22,
|
||||
DestUser: "testuser",
|
||||
DestinationPath: "/remote/path",
|
||||
DestKeyFile: "private_key_content",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
|
||||
err = db.CreateTransferConfig(sftpConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test generating rclone config
|
||||
err = db.GenerateRcloneConfig(sftpConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// FTP config test
|
||||
ftpConfig := &TransferConfig{
|
||||
Name: "Test FTP Config",
|
||||
SourceType: "local",
|
||||
SourcePath: "/local/ftp",
|
||||
DestinationType: "ftp",
|
||||
DestHost: "ftp.example.com",
|
||||
DestPort: 21,
|
||||
DestUser: "ftpuser",
|
||||
DestPassiveMode: true,
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
|
||||
err = db.CreateTransferConfig(ftpConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test generating rclone config
|
||||
err = db.GenerateRcloneConfig(ftpConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// S3 config test
|
||||
s3Config := &TransferConfig{
|
||||
Name: "Test S3 Config",
|
||||
SourceType: "local",
|
||||
SourcePath: "/local/s3",
|
||||
DestinationType: "s3",
|
||||
DestBucket: "mybucket",
|
||||
DestAccessKey: "accessKey",
|
||||
DestRegion: "us-east-1",
|
||||
DestEndpoint: "s3.amazonaws.com",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
|
||||
err = db.CreateTransferConfig(s3Config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test generating rclone config
|
||||
err = db.GenerateRcloneConfig(s3Config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test generating config for unsupported protocol
|
||||
invalidConfig := &TransferConfig{
|
||||
Name: "Invalid Protocol Config",
|
||||
SourceType: "local",
|
||||
SourcePath: "/local/path",
|
||||
DestinationType: "unsupported",
|
||||
DestHost: "example.com",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
|
||||
err = db.CreateTransferConfig(invalidConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// This should NOT return an error for unsupported protocol
|
||||
// as it defaults to local type
|
||||
err = db.GenerateRcloneConfig(invalidConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify the config file exists
|
||||
configPath := db.GetConfigRclonePath(invalidConfig)
|
||||
_, err = os.Stat(configPath)
|
||||
assert.NoError(t, err, "Config file should exist")
|
||||
}
|
||||
|
||||
func TestUpdateJobStatus(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a test user
|
||||
testUser := &User{
|
||||
Email: "job-status-test@example.com",
|
||||
PasswordHash: "hashed_password",
|
||||
IsAdmin: false,
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
|
||||
err := db.CreateUser(testUser)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a test transfer config
|
||||
testConfig := &TransferConfig{
|
||||
Name: "Test Config for Job Status",
|
||||
SourceType: "local",
|
||||
SourcePath: "/source/path",
|
||||
DestinationType: "local",
|
||||
DestinationPath: "/destination/path",
|
||||
FilePattern: "*.txt",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
|
||||
err = db.CreateTransferConfig(testConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a test job
|
||||
now := time.Now()
|
||||
lastRun := now.Add(-time.Hour)
|
||||
nextRun := now.Add(time.Hour)
|
||||
|
||||
testJob := &Job{
|
||||
Name: "Test Job Status",
|
||||
ConfigID: testConfig.ID,
|
||||
Schedule: "0 * * * *", // Run hourly
|
||||
Enabled: true,
|
||||
LastRun: &lastRun,
|
||||
NextRun: &nextRun,
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
|
||||
err = db.CreateJob(testJob)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Update job's last run time
|
||||
updatedLastRun := time.Now()
|
||||
testJob.LastRun = &updatedLastRun
|
||||
|
||||
err = db.UpdateJobStatus(testJob)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify the job was updated
|
||||
updatedJob, err := db.GetJob(testJob.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, lastRun.Unix(), updatedJob.LastRun.Unix())
|
||||
|
||||
// Update job's next run time
|
||||
updatedNextRun := time.Now().Add(2 * time.Hour)
|
||||
testJob.NextRun = &updatedNextRun
|
||||
|
||||
err = db.UpdateJobStatus(testJob)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify the job was updated again
|
||||
updatedJob, err = db.GetJob(testJob.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, updatedNextRun.Unix(), updatedJob.NextRun.Unix())
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestDeleteTransferConfigEdgeCases tests edge cases for the DeleteTransferConfig function
|
||||
func TestDeleteTransferConfigEdgeCases(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a test user
|
||||
testUser := &User{
|
||||
Email: "config-edge-test@example.com",
|
||||
PasswordHash: "hashed_password",
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
err := db.CreateUser(testUser)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create multiple configs
|
||||
configs := make([]*TransferConfig, 5)
|
||||
for i := 0; i < 5; i++ {
|
||||
config := &TransferConfig{
|
||||
Name: fmt.Sprintf("Edge Config %d", i),
|
||||
SourceType: "local",
|
||||
SourcePath: fmt.Sprintf("/source/path/%d", i),
|
||||
DestinationType: "local",
|
||||
DestinationPath: fmt.Sprintf("/destination/path/%d", i),
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateTransferConfig(config)
|
||||
assert.NoError(t, err)
|
||||
configs[i] = config
|
||||
}
|
||||
|
||||
// Delete them in reverse order
|
||||
for i := 4; i >= 0; i-- {
|
||||
err = db.DeleteTransferConfig(configs[i].ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify deletion
|
||||
_, err = db.GetTransferConfig(configs[i].ID)
|
||||
assert.Error(t, err, "Config should be deleted")
|
||||
}
|
||||
|
||||
// Test deleting a config that has a job associated with it
|
||||
configWithJob := &TransferConfig{
|
||||
Name: "Config with Job",
|
||||
SourceType: "local",
|
||||
SourcePath: "/source/path/job",
|
||||
DestinationType: "local",
|
||||
DestinationPath: "/destination/path/job",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateTransferConfig(configWithJob)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a job for this config
|
||||
job := &Job{
|
||||
Name: "Job for Config",
|
||||
ConfigID: configWithJob.ID,
|
||||
Schedule: "0 * * * *",
|
||||
Enabled: true,
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateJob(job)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Try to delete the config - this should fail due to foreign key constraint
|
||||
err = db.DeleteTransferConfig(configWithJob.ID)
|
||||
assert.Error(t, err, "Should not be able to delete config with associated jobs")
|
||||
assert.Contains(t, err.Error(), "jobs are using this configuration", "Error should mention jobs")
|
||||
|
||||
// Delete the job first
|
||||
err = db.DeleteJob(job.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Now delete the config - this should succeed
|
||||
err = db.DeleteTransferConfig(configWithJob.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify deletion
|
||||
_, err = db.GetTransferConfig(configWithJob.ID)
|
||||
assert.Error(t, err, "Config should be deleted")
|
||||
}
|
||||
|
||||
// TestDeleteJobEdgeCases tests edge cases for the DeleteJob function
|
||||
func TestDeleteJobEdgeCases(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a test user
|
||||
testUser := &User{
|
||||
Email: "job-edge-test@example.com",
|
||||
PasswordHash: "hashed_password",
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
err := db.CreateUser(testUser)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a test config
|
||||
config := &TransferConfig{
|
||||
Name: "Config for Job Edge Cases",
|
||||
SourceType: "local",
|
||||
SourcePath: "/source/path",
|
||||
DestinationType: "local",
|
||||
DestinationPath: "/destination/path",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateTransferConfig(config)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create multiple jobs
|
||||
jobs := make([]*Job, 5)
|
||||
for i := 0; i < 5; i++ {
|
||||
job := &Job{
|
||||
Name: fmt.Sprintf("Edge Job %d", i),
|
||||
ConfigID: config.ID,
|
||||
Schedule: "0 * * * *",
|
||||
Enabled: true,
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateJob(job)
|
||||
assert.NoError(t, err)
|
||||
jobs[i] = job
|
||||
}
|
||||
|
||||
// Delete them in reverse order
|
||||
for i := 4; i >= 0; i-- {
|
||||
err = db.DeleteJob(jobs[i].ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify deletion
|
||||
_, err = db.GetJob(jobs[i].ID)
|
||||
assert.Error(t, err, "Job should be deleted")
|
||||
}
|
||||
|
||||
// Create a job with history records
|
||||
jobWithHistory := &Job{
|
||||
Name: "Job with History",
|
||||
ConfigID: config.ID,
|
||||
Schedule: "0 * * * *",
|
||||
Enabled: true,
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateJob(jobWithHistory)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create history records
|
||||
for i := 0; i < 3; i++ {
|
||||
startTime := time.Now().Add(time.Duration(-i) * time.Hour)
|
||||
endTime := startTime.Add(30 * time.Minute)
|
||||
history := &JobHistory{
|
||||
JobID: jobWithHistory.ID,
|
||||
StartTime: startTime,
|
||||
EndTime: &endTime,
|
||||
Status: "completed",
|
||||
BytesTransferred: int64(1024 * (i + 1)),
|
||||
FilesTransferred: i + 1,
|
||||
}
|
||||
err = db.CreateJobHistory(history)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// Now delete the job - this should succeed even with history records
|
||||
// (due to foreign key constraints in the database)
|
||||
err = db.DeleteJob(jobWithHistory.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify deletion
|
||||
_, err = db.GetJob(jobWithHistory.ID)
|
||||
assert.Error(t, err, "Job should be deleted")
|
||||
}
|
||||
|
||||
// TestInitializeEdgeCases tests edge cases for the Initialize function
|
||||
func TestInitializeEdgeCases(t *testing.T) {
|
||||
// Test with a read-only directory (if possible)
|
||||
tempDir, err := os.MkdirTemp("", "gomft_test_readonly")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Try to make the directory read-only
|
||||
// Note: This may not work on all systems due to permissions
|
||||
origPerms, err := os.Stat(tempDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to stat directory: %v", err)
|
||||
}
|
||||
|
||||
// Try to make it read-only
|
||||
err = os.Chmod(tempDir, 0400) // read-only
|
||||
if err != nil {
|
||||
t.Logf("Warning: Could not set directory to read-only: %v", err)
|
||||
t.Skip("Could not set directory to read-only, skipping test")
|
||||
}
|
||||
defer os.Chmod(tempDir, origPerms.Mode()) // restore original permissions
|
||||
|
||||
dbPath := filepath.Join(tempDir, "readonly.db")
|
||||
// This might fail because the directory is read-only
|
||||
db, err := Initialize(dbPath)
|
||||
if err != nil {
|
||||
// Expected error due to read-only directory
|
||||
t.Logf("Got expected error for read-only directory: %v", err)
|
||||
} else {
|
||||
// If it succeeded, clean up
|
||||
t.Logf("Warning: DB initialization succeeded even with read-only directory!")
|
||||
err = db.Close()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCloseEdgeCases tests edge cases for the Close function
|
||||
func TestCloseEdgeCases(t *testing.T) {
|
||||
// Create a temporary database
|
||||
tempDir, err := os.MkdirTemp("", "gomft_test_close_edge")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
dbPath := filepath.Join(tempDir, "close_edge.db")
|
||||
db, err := Initialize(dbPath)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test calling methods after close
|
||||
sqlDB, err := db.DB.DB()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Get initial stats
|
||||
stats := sqlDB.Stats()
|
||||
t.Logf("Initial stats: MaxOpenConnections=%d, OpenConnections=%d, InUse=%d",
|
||||
stats.MaxOpenConnections, stats.OpenConnections, stats.InUse)
|
||||
|
||||
// Close the DB
|
||||
err = db.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Try to get stats again - this might fail
|
||||
stats = sqlDB.Stats()
|
||||
t.Logf("After close stats: MaxOpenConnections=%d, OpenConnections=%d, InUse=%d",
|
||||
stats.MaxOpenConnections, stats.OpenConnections, stats.InUse)
|
||||
|
||||
// Verify that DB operations fail after close
|
||||
_, err = db.GetUserByEmail("test@example.com")
|
||||
assert.Error(t, err, "DB operations should fail after close")
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Tests for error handling in GetUserByEmail
|
||||
func TestGetUserByEmailError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Test the error case with a non-existent email
|
||||
user, err := db.GetUserByEmail("nonexistent@example.com")
|
||||
|
||||
// Verify expectations
|
||||
assert.Error(t, err, "Should return an error when user is not found")
|
||||
assert.Nil(t, user, "User should be nil when an error occurs")
|
||||
}
|
||||
|
||||
// Tests for error handling in GetUserByID
|
||||
func TestGetUserByIDError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Test the error case with a non-existent ID
|
||||
user, err := db.GetUserByID(9999)
|
||||
|
||||
// Verify expectations
|
||||
assert.Error(t, err, "Should return an error when user is not found")
|
||||
assert.Nil(t, user, "User should be nil when an error occurs")
|
||||
}
|
||||
|
||||
// Tests for error handling in GetPasswordResetToken
|
||||
func TestGetPasswordResetTokenError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Test the error case with an invalid token
|
||||
token, err := db.GetPasswordResetToken("invalid-token")
|
||||
|
||||
// Verify expectations
|
||||
assert.Error(t, err, "Should return an error when token is not found")
|
||||
assert.Nil(t, token, "Token should be nil when an error occurs")
|
||||
|
||||
// Test with an expired token
|
||||
testUser := &User{
|
||||
Email: "expired-token@example.com",
|
||||
PasswordHash: "hashed_password",
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
err = db.CreateUser(testUser)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create an expired token (expired 1 hour ago)
|
||||
expiredToken := &PasswordResetToken{
|
||||
UserID: testUser.ID,
|
||||
Token: "expired-token",
|
||||
ExpiresAt: time.Now().Add(-1 * time.Hour),
|
||||
}
|
||||
err = db.CreatePasswordResetToken(expiredToken)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Try to get the expired token
|
||||
retrievedToken, err := db.GetPasswordResetToken("expired-token")
|
||||
assert.Error(t, err, "Should return an error for expired token")
|
||||
assert.Nil(t, retrievedToken, "Token should be nil for expired token")
|
||||
|
||||
// Create a used token
|
||||
usedToken := &PasswordResetToken{
|
||||
UserID: testUser.ID,
|
||||
Token: "used-token",
|
||||
ExpiresAt: time.Now().Add(1 * time.Hour),
|
||||
Used: true,
|
||||
}
|
||||
err = db.CreatePasswordResetToken(usedToken)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Try to get the used token
|
||||
retrievedToken, err = db.GetPasswordResetToken("used-token")
|
||||
assert.Error(t, err, "Should return an error for used token")
|
||||
assert.Nil(t, retrievedToken, "Token should be nil for used token")
|
||||
}
|
||||
|
||||
// Tests for error handling in DeleteTransferConfig
|
||||
func TestDeleteTransferConfigError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Test deleting a non-existent config
|
||||
err := db.DeleteTransferConfig(9999)
|
||||
|
||||
// Verify expectations - should not return an error even if the record doesn't exist
|
||||
assert.NoError(t, err, "Should not return an error when deleting non-existent config")
|
||||
}
|
||||
|
||||
// Tests for error handling in DeleteJob
|
||||
func TestDeleteJobError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Test deleting a non-existent job
|
||||
err := db.DeleteJob(9999)
|
||||
|
||||
// Verify expectations - should not return an error even if the record doesn't exist
|
||||
assert.NoError(t, err, "Should not return an error when deleting non-existent job")
|
||||
}
|
||||
|
||||
// Tests for error handling in GetFileMetadataByHash
|
||||
func TestGetFileMetadataByHashError(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Test the error case with an invalid hash
|
||||
metadata, err := db.GetFileMetadataByHash("invalid-hash")
|
||||
|
||||
// Verify expectations
|
||||
assert.Error(t, err, "Should return an error when metadata is not found")
|
||||
assert.Nil(t, metadata, "Metadata should be nil when an error occurs")
|
||||
}
|
||||
|
||||
// Tests for error handling in Initialize
|
||||
func TestInitializeErrors(t *testing.T) {
|
||||
// Test with a path that is a directory, not a file
|
||||
// This should cause an error when trying to open a SQLite database
|
||||
_, err := Initialize("/dev/null/cannot_be_a_db")
|
||||
assert.Error(t, err, "Should return an error with invalid path")
|
||||
}
|
||||
|
||||
// Tests for error handling in GenerateRcloneConfig
|
||||
func TestGenerateRcloneConfigErrors(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a test user
|
||||
testUser := &User{
|
||||
Email: "config-error-test@example.com",
|
||||
PasswordHash: "hashed_password",
|
||||
IsAdmin: false,
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
|
||||
err := db.CreateUser(testUser)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a config with invalid credentials for an SFTP connection
|
||||
invalidConfig := &TransferConfig{
|
||||
Name: "Invalid Config",
|
||||
SourceType: "sftp", // Using SFTP with invalid host to force error
|
||||
SourceHost: "nonexistent.host",
|
||||
SourcePort: 22,
|
||||
SourceUser: "invaliduser",
|
||||
SourcePath: "/source/path",
|
||||
DestinationType: "local",
|
||||
DestinationPath: "/destination/path",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
|
||||
err = db.CreateTransferConfig(invalidConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Set a non-existent RCLONE_PATH to force error
|
||||
t.Setenv("RCLONE_PATH", "/nonexistent/rclone")
|
||||
|
||||
// This should return an error because the rclone command doesn't exist
|
||||
err = db.GenerateRcloneConfig(invalidConfig)
|
||||
assert.Error(t, err, "Should return an error when rclone command fails")
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestInitializeWithNonExistentDirectory tests initialization with a directory that doesn't exist
|
||||
func TestInitializeWithNonExistentDirectory(t *testing.T) {
|
||||
// Create a temporary directory path
|
||||
tempDir := filepath.Join(os.TempDir(), "gomft_test_nonexistent")
|
||||
|
||||
// Make sure the directory doesn't exist
|
||||
_ = os.RemoveAll(tempDir)
|
||||
|
||||
// Create a path inside the non-existent directory
|
||||
dbPath := filepath.Join(tempDir, "test.db")
|
||||
|
||||
// Initialize the database - this should create the directory
|
||||
db, err := Initialize(dbPath)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, db)
|
||||
|
||||
// Verify the directory was created
|
||||
_, err = os.Stat(tempDir)
|
||||
assert.NoError(t, err, "Directory should be created")
|
||||
|
||||
// Close and clean up
|
||||
err = db.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Clean up
|
||||
_ = os.RemoveAll(tempDir)
|
||||
}
|
||||
|
||||
// TestInitializeWithInvalidDBPath tests initialization with an invalid DB path
|
||||
func TestInitializeWithInvalidDBPath(t *testing.T) {
|
||||
// Create a file path that can't be a SQLite database
|
||||
invalidPath := "/dev/null/invalid.db"
|
||||
|
||||
// Attempt to initialize with an invalid path
|
||||
db, err := Initialize(invalidPath)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, db)
|
||||
}
|
||||
|
||||
// TestInitializeWithExistingDB tests initialization with an existing database
|
||||
func TestInitializeWithExistingDB(t *testing.T) {
|
||||
// Create a temporary directory
|
||||
tempDir, err := os.MkdirTemp("", "gomft_test_existing")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create a database path
|
||||
dbPath := filepath.Join(tempDir, "existing.db")
|
||||
|
||||
// Initialize the database for the first time
|
||||
db1, err := Initialize(dbPath)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, db1)
|
||||
|
||||
// Create a test user to verify the database works
|
||||
user := &User{
|
||||
Email: "test@example.com",
|
||||
PasswordHash: "hash",
|
||||
IsAdmin: true,
|
||||
}
|
||||
err = db1.CreateUser(user)
|
||||
assert.NoError(t, err)
|
||||
assert.NotZero(t, user.ID)
|
||||
|
||||
// Close the first database connection
|
||||
err = db1.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Initialize the database again with the same path
|
||||
db2, err := Initialize(dbPath)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, db2)
|
||||
|
||||
// Verify we can read the user that was created earlier
|
||||
retrievedUser, err := db2.GetUserByEmail("test@example.com")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, user.ID, retrievedUser.ID)
|
||||
|
||||
// Close the second database connection
|
||||
err = db2.Close()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// TestCloseMultipleTimes tests closing the database multiple times
|
||||
func TestCloseMultipleTimes(t *testing.T) {
|
||||
// Create a temporary directory
|
||||
tempDir, err := os.MkdirTemp("", "gomft_test_close")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// Create a database path
|
||||
dbPath := filepath.Join(tempDir, "close.db")
|
||||
|
||||
// Initialize the database
|
||||
db, err := Initialize(dbPath)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, db)
|
||||
|
||||
// Close the database
|
||||
err = db.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Trying to close it again - for some DB drivers this might cause an error
|
||||
// but SQLite in-memory seems to handle this gracefully
|
||||
err = db.Close()
|
||||
// We won't assert error here since it depends on the driver
|
||||
t.Logf("Second close resulted in: %v", err)
|
||||
|
||||
// Instead, let's test that DB operations fail after close
|
||||
_, err = db.GetUserByEmail("test@example.com")
|
||||
assert.Error(t, err, "DB operations should fail after close")
|
||||
}
|
||||
|
||||
// TestInitializeWithMigrationFailure tests when AutoMigrate fails
|
||||
func TestInitializeWithMigrationFailure(t *testing.T) {
|
||||
// We can't easily cause a migration failure with SQLite
|
||||
// but we can skip this test and document that it's hard to test
|
||||
t.Skip("Testing migration failure is difficult with SQLite")
|
||||
|
||||
// In a real-world scenario, this might happen if:
|
||||
// 1. The schema changed significantly between versions
|
||||
// 2. The database is corrupted
|
||||
// 3. There are permission issues
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestGetConfigRclonePathWithEnv tests the GetConfigRclonePath function with different environment variables
|
||||
func TestGetConfigRclonePathWithEnv(t *testing.T) {
|
||||
// Save original environment variable
|
||||
originalDataDir := os.Getenv("DATA_DIR")
|
||||
defer os.Setenv("DATA_DIR", originalDataDir)
|
||||
|
||||
// Set a custom data directory
|
||||
customDir := "/tmp/custom_data_dir"
|
||||
os.Setenv("DATA_DIR", customDir)
|
||||
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a test config
|
||||
testUser := &User{
|
||||
Email: "rclone-env-test@example.com",
|
||||
PasswordHash: "hashed_password",
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
err := db.CreateUser(testUser)
|
||||
assert.NoError(t, err)
|
||||
|
||||
testConfig := &TransferConfig{
|
||||
Name: "Test Config",
|
||||
SourceType: "local",
|
||||
SourcePath: "/source/path",
|
||||
DestinationType: "local",
|
||||
DestinationPath: "/dest/path",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateTransferConfig(testConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test GetConfigRclonePath with custom DATA_DIR
|
||||
configPath := db.GetConfigRclonePath(testConfig)
|
||||
assert.Equal(t,
|
||||
filepath.Join(customDir, "configs", fmt.Sprintf("config_%d.conf", testConfig.ID)),
|
||||
configPath,
|
||||
"Should use DATA_DIR environment variable")
|
||||
}
|
||||
|
||||
// TestGenerateRcloneConfigWithoutRclone tests error handling when rclone executable is not available
|
||||
func TestGenerateRcloneConfigWithoutRclone(t *testing.T) {
|
||||
// Save original environment variable
|
||||
originalRclonePath := os.Getenv("RCLONE_PATH")
|
||||
defer os.Setenv("RCLONE_PATH", originalRclonePath)
|
||||
|
||||
// Set a nonexistent rclone path
|
||||
os.Setenv("RCLONE_PATH", "/nonexistent/rclone")
|
||||
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a test user
|
||||
testUser := &User{
|
||||
Email: "rclone-missing-test@example.com",
|
||||
PasswordHash: "hashed_password",
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
err := db.CreateUser(testUser)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test configs for different source types
|
||||
sourceTypes := []string{"sftp", "s3", "minio", "b2", "smb", "ftp", "webdav", "nextcloud", "onedrive", "google_drive"}
|
||||
|
||||
for _, sourceType := range sourceTypes {
|
||||
testConfig := &TransferConfig{
|
||||
Name: fmt.Sprintf("Test %s Config", sourceType),
|
||||
SourceType: sourceType,
|
||||
SourceHost: "example.com",
|
||||
SourcePort: 22,
|
||||
SourceUser: "testuser",
|
||||
SourcePath: "/source/path",
|
||||
SourceAccessKey: "access_key",
|
||||
SourceSecretKey: "secret_key",
|
||||
SourceRegion: "us-east-1",
|
||||
SourceEndpoint: "endpoint.example.com",
|
||||
SourceClientID: "client_id",
|
||||
SourceClientSecret: "client_secret",
|
||||
DestinationType: "local",
|
||||
DestinationPath: "/dest/path",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
|
||||
err = db.CreateTransferConfig(testConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// This should return an error because rclone is not available
|
||||
err = db.GenerateRcloneConfig(testConfig)
|
||||
assert.Error(t, err, "Should return an error when rclone executable is not found for source type: %s", sourceType)
|
||||
}
|
||||
|
||||
// Test configs for different destination types
|
||||
destTypes := []string{"sftp", "s3", "minio", "b2", "smb", "ftp", "webdav", "nextcloud", "onedrive", "google_drive"}
|
||||
|
||||
for _, destType := range destTypes {
|
||||
testConfig := &TransferConfig{
|
||||
Name: fmt.Sprintf("Test Dest %s Config", destType),
|
||||
SourceType: "local",
|
||||
SourcePath: "/source/path",
|
||||
DestinationType: destType,
|
||||
DestHost: "example.com",
|
||||
DestPort: 22,
|
||||
DestUser: "testuser",
|
||||
DestinationPath: "/dest/path",
|
||||
DestAccessKey: "access_key",
|
||||
DestSecretKey: "secret_key",
|
||||
DestRegion: "us-east-1",
|
||||
DestEndpoint: "endpoint.example.com",
|
||||
DestClientID: "client_id",
|
||||
DestClientSecret: "client_secret",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
|
||||
err = db.CreateTransferConfig(testConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// This should return an error because rclone is not available
|
||||
err = db.GenerateRcloneConfig(testConfig)
|
||||
if destType != "local" {
|
||||
assert.Error(t, err, "Should return an error when rclone executable is not found for dest type: %s", destType)
|
||||
} else {
|
||||
// Local destination type might not error since it doesn't need to call rclone
|
||||
t.Logf("Local destination type might not error")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// TestDeleteTransferConfigWithTransaction tests the DeleteTransferConfig function with transaction scenarios
|
||||
func TestDeleteTransferConfigWithTransaction(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a test user
|
||||
testUser := &User{
|
||||
Email: "delete-config-test@example.com",
|
||||
PasswordHash: "hashed_password",
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
err := db.CreateUser(testUser)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a test config
|
||||
testConfig := &TransferConfig{
|
||||
Name: "Test Delete Config",
|
||||
SourceType: "local",
|
||||
SourcePath: "/source/path",
|
||||
DestinationType: "local",
|
||||
DestinationPath: "/destination/path",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateTransferConfig(testConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test successful deletion
|
||||
err = db.DeleteTransferConfig(testConfig.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify deletion
|
||||
_, err = db.GetTransferConfig(testConfig.ID)
|
||||
assert.Error(t, err, "Config should be deleted")
|
||||
|
||||
// Test deletion with transaction that's rolled back
|
||||
// Create another config
|
||||
testConfig2 := &TransferConfig{
|
||||
Name: "Test Delete Config 2",
|
||||
SourceType: "local",
|
||||
SourcePath: "/source/path2",
|
||||
DestinationType: "local",
|
||||
DestinationPath: "/destination/path2",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateTransferConfig(testConfig2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Start a transaction
|
||||
tx := db.Begin()
|
||||
assert.NotNil(t, tx)
|
||||
|
||||
// Delete the config within the transaction
|
||||
err = tx.Delete(&TransferConfig{}, testConfig2.ID).Error
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Rollback the transaction
|
||||
tx.Rollback()
|
||||
|
||||
// Verify the config still exists
|
||||
config, err := db.GetTransferConfig(testConfig2.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, config)
|
||||
assert.Equal(t, testConfig2.ID, config.ID)
|
||||
|
||||
// Test deletion with a committed transaction
|
||||
tx = db.Begin()
|
||||
assert.NotNil(t, tx)
|
||||
|
||||
// Delete the config within the transaction
|
||||
err = tx.Delete(&TransferConfig{}, testConfig2.ID).Error
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Commit the transaction
|
||||
tx.Commit()
|
||||
|
||||
// Verify the config is deleted
|
||||
_, err = db.GetTransferConfig(testConfig2.ID)
|
||||
assert.Error(t, err, "Config should be deleted after commit")
|
||||
}
|
||||
|
||||
// TestDeleteJobWithTransaction tests the DeleteJob function with transaction scenarios
|
||||
func TestDeleteJobWithTransaction(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Create a test user
|
||||
testUser := &User{
|
||||
Email: "delete-job-test@example.com",
|
||||
PasswordHash: "hashed_password",
|
||||
LastPasswordChange: time.Now(),
|
||||
}
|
||||
err := db.CreateUser(testUser)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a test transfer config
|
||||
testConfig := &TransferConfig{
|
||||
Name: "Test Delete Job Config",
|
||||
SourceType: "local",
|
||||
SourcePath: "/source/path",
|
||||
DestinationType: "local",
|
||||
DestinationPath: "/destination/path",
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateTransferConfig(testConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a test job
|
||||
testJob := &Job{
|
||||
Name: "Test Delete Job",
|
||||
ConfigID: testConfig.ID,
|
||||
Schedule: "0 * * * *", // Run hourly
|
||||
Enabled: true,
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateJob(testJob)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test successful deletion
|
||||
err = db.DeleteJob(testJob.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify deletion
|
||||
_, err = db.GetJob(testJob.ID)
|
||||
assert.Error(t, err, "Job should be deleted")
|
||||
|
||||
// Test deletion with transaction that's rolled back
|
||||
// Create another job
|
||||
testJob2 := &Job{
|
||||
Name: "Test Delete Job 2",
|
||||
ConfigID: testConfig.ID,
|
||||
Schedule: "0 * * * *", // Run hourly
|
||||
Enabled: true,
|
||||
CreatedBy: testUser.ID,
|
||||
}
|
||||
err = db.CreateJob(testJob2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Start a transaction
|
||||
tx := db.Begin()
|
||||
assert.NotNil(t, tx)
|
||||
|
||||
// Delete the job within the transaction
|
||||
err = tx.Delete(&Job{}, testJob2.ID).Error
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Rollback the transaction
|
||||
tx.Rollback()
|
||||
|
||||
// Verify the job still exists
|
||||
job, err := db.GetJob(testJob2.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, job)
|
||||
assert.Equal(t, testJob2.ID, job.ID)
|
||||
|
||||
// Test deletion with a committed transaction
|
||||
tx = db.Begin()
|
||||
assert.NotNil(t, tx)
|
||||
|
||||
// Delete the job within the transaction
|
||||
err = tx.Delete(&Job{}, testJob2.ID).Error
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Commit the transaction
|
||||
tx.Commit()
|
||||
|
||||
// Verify the job is deleted
|
||||
_, err = db.GetJob(testJob2.ID)
|
||||
assert.Error(t, err, "Job should be deleted after commit")
|
||||
}
|
||||
|
||||
// TestTransactionHelpers tests transaction helper methods
|
||||
func TestTransactionHelpers(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
|
||||
// Test Begin and Rollback
|
||||
tx := db.Begin()
|
||||
assert.NotNil(t, tx)
|
||||
assert.IsType(t, &gorm.DB{}, tx)
|
||||
|
||||
// Rollback should succeed
|
||||
err := tx.Rollback().Error
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test Begin and Commit
|
||||
tx = db.Begin()
|
||||
assert.NotNil(t, tx)
|
||||
|
||||
// Commit should succeed
|
||||
err = tx.Commit().Error
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
Reference in new issue
Block a user