Files
GoMFT/internal/scheduler/scheduler_test.go
T
StarFleetCPTN d1967b4402 feat: Add comprehensive tests for providers and scheduler functionalities
- Introduce new test file for providers, covering integration and rendering of source and destination components
- Implement tests for source and destination provider availability
- Enhance scheduler tests with new scenarios for job unscheduling, log rotation, and file processing history
- Add tests for output pattern processing with date and filename variables
- Ensure accessibility and dynamic rendering of provider forms in tests
2025-03-13 19:02:02 -07:00

1118 lines
30 KiB
Go

package scheduler
import (
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
"github.com/glebarez/sqlite"
"github.com/robfig/cron/v3"
"github.com/starfleetcptn/gomft/internal/db"
"github.com/stretchr/testify/assert"
"gorm.io/gorm"
)
// setupTestDB creates an in-memory SQLite database for testing
func setupTestDB(t *testing.T) *db.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(
&db.User{},
&db.PasswordHistory{},
&db.PasswordResetToken{},
&db.TransferConfig{},
&db.Job{},
&db.JobHistory{},
&db.FileMetadata{},
)
if err != nil {
t.Fatalf("Failed to migrate database: %v", err)
}
return &db.DB{DB: gormDB}
}
func TestLogLevel(t *testing.T) {
tests := []struct {
level LogLevel
expected string
}{
{LogLevelError, "error"},
{LogLevelInfo, "info"},
{LogLevelDebug, "debug"},
{LogLevel(99), "unknown"}, // Invalid level
}
for _, tc := range tests {
t.Run(tc.expected, func(t *testing.T) {
if tc.level.String() != tc.expected {
t.Errorf("Expected %s, got %s", tc.expected, tc.level.String())
}
})
}
}
func TestParseLogLevel(t *testing.T) {
tests := []struct {
input string
expected LogLevel
}{
{"error", LogLevelError},
{"info", LogLevelInfo},
{"debug", LogLevelDebug},
{"ERROR", LogLevelError}, // Case insensitivity
{"INFO", LogLevelInfo}, // Case insensitivity
{"DEBUG", LogLevelDebug}, // Case insensitivity
{"invalid", LogLevelInfo}, // Default to info
}
for _, tc := range tests {
t.Run(tc.input, func(t *testing.T) {
if ParseLogLevel(tc.input) != tc.expected {
t.Errorf("Expected %v, got %v", tc.expected, ParseLogLevel(tc.input))
}
})
}
}
func TestScheduler_New(t *testing.T) {
// Set up a temporary data directory for logs
tempDir, err := os.MkdirTemp("", "gomft-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
t.Cleanup(func() {
os.RemoveAll(tempDir)
})
// Set DATA_DIR environment variable for the test
originalDataDir := os.Getenv("DATA_DIR")
os.Setenv("DATA_DIR", tempDir)
defer os.Setenv("DATA_DIR", originalDataDir)
// Create a test database
database := setupTestDB(t)
// Create a new scheduler
scheduler := New(database)
// Check that the scheduler was created successfully
if scheduler == nil {
t.Fatalf("Expected scheduler to be created, got nil")
}
// Check that the scheduler has the expected properties
if scheduler.db != database {
t.Errorf("Expected scheduler.db to be the test database")
}
if scheduler.cron == nil {
t.Errorf("Expected scheduler.cron to be initialized")
}
if scheduler.jobs == nil {
t.Errorf("Expected scheduler.jobs to be initialized")
}
if scheduler.log == nil {
t.Errorf("Expected scheduler.log to be initialized")
}
// Stop the scheduler to clean up
scheduler.Stop()
}
func TestScheduler_ScheduleJob(t *testing.T) {
// Set up a temporary data directory for logs
tempDir, err := os.MkdirTemp("", "gomft-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
t.Cleanup(func() {
os.RemoveAll(tempDir)
})
// Set DATA_DIR environment variable for the test
originalDataDir := os.Getenv("DATA_DIR")
os.Setenv("DATA_DIR", tempDir)
defer os.Setenv("DATA_DIR", originalDataDir)
// Create a test database
database := setupTestDB(t)
// Create a test user
user := &db.User{
Email: "test@example.com",
PasswordHash: "hashed_password",
IsAdmin: true,
}
if err := database.CreateUser(user); err != nil {
t.Fatalf("Failed to create test user: %v", err)
}
// Create a test transfer config
config := &db.TransferConfig{
Name: "Test Config",
SourceType: "local",
SourcePath: "/source",
DestinationType: "local",
DestinationPath: "/dest",
CreatedBy: user.ID,
}
if err := database.DB.Create(config).Error; err != nil {
t.Fatalf("Failed to create transfer config: %v", err)
}
// Create a test job
job := &db.Job{
Name: "Test Job",
Schedule: "*/5 * * * *", // Every 5 minutes
ConfigID: config.ID,
Enabled: true,
CreatedBy: user.ID,
}
if err := database.DB.Create(job).Error; err != nil {
t.Fatalf("Failed to create job: %v", err)
}
// Create a new scheduler
scheduler := New(database)
t.Cleanup(func() {
scheduler.Stop()
})
// Schedule the job
if err := scheduler.ScheduleJob(job); err != nil {
t.Fatalf("Failed to schedule job: %v", err)
}
// Check that the job was scheduled
scheduler.jobMutex.Lock()
_, exists := scheduler.jobs[job.ID]
scheduler.jobMutex.Unlock()
if !exists {
t.Errorf("Expected job to be scheduled, but it wasn't")
}
// Check that the next run time was set
if job.NextRun == nil {
t.Errorf("Expected NextRun to be set, got nil")
}
// Test scheduling a disabled job
job.Enabled = false
if err := scheduler.ScheduleJob(job); err != nil {
t.Fatalf("Failed to schedule disabled job: %v", err)
}
// Check that the disabled job was not scheduled
scheduler.jobMutex.Lock()
_, exists = scheduler.jobs[job.ID]
scheduler.jobMutex.Unlock()
if exists {
t.Errorf("Expected disabled job not to be scheduled, but it was")
}
// Test with invalid cron expression
job.Enabled = true
job.Schedule = "invalid cron"
if err := scheduler.ScheduleJob(job); err == nil {
t.Errorf("Expected error for invalid cron expression, got nil")
}
}
func TestProcessOutputPattern(t *testing.T) {
tests := []struct {
name string
pattern string
filename string
expected string
}{
{
name: "No placeholders",
pattern: "output.txt",
filename: "input.txt",
expected: "output.txt",
},
{
name: "Filename placeholder",
pattern: "${filename}",
filename: "input.txt",
expected: "input",
},
{
name: "Extension placeholder",
pattern: "output${ext}",
filename: "input.txt",
expected: "output.txt",
},
{
name: "Filename and extension placeholders",
pattern: "${filename}${ext}",
filename: "input.txt",
expected: "input.txt",
},
{
name: "Prefix and suffix",
pattern: "prefix_${filename}_suffix${ext}",
filename: "input.txt",
expected: "prefix_input_suffix.txt",
},
// Add more test cases for timestamp, date placeholders, etc.
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := ProcessOutputPattern(tc.pattern, tc.filename)
// For patterns with date placeholders, just check that the result contains expected parts
if strings.Contains(tc.pattern, "${date:") {
// Just check that the date format was applied
assert.NotEqual(t, tc.pattern, result)
} else {
assert.Equal(t, tc.expected, result)
}
})
}
}
func TestProcessOutputPatternWithDateTimeVariables(t *testing.T) {
// Test with static patterns (we can't easily mock time.Now() without changing the implementation)
tests := []struct {
name string
pattern string
filename string
expected string
}{
{
name: "No placeholders",
pattern: "output.txt",
filename: "input.txt",
expected: "output.txt",
},
{
name: "Filename only",
pattern: "${filename}",
filename: "input.txt",
expected: "input",
},
{
name: "Extension only",
pattern: "${ext}",
filename: "input.txt",
expected: ".txt",
},
{
name: "Filename and extension",
pattern: "${filename}${ext}",
filename: "document.docx",
expected: "document.docx",
},
{
name: "Custom pattern with filename",
pattern: "processed_${filename}",
filename: "data.csv",
expected: "processed_data",
},
{
name: "Custom pattern with extension",
pattern: "backup${ext}",
filename: "image.png",
expected: "backup.png",
},
{
name: "Custom pattern with filename and extension",
pattern: "${filename}_copy${ext}",
filename: "report.pdf",
expected: "report_copy.pdf",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := ProcessOutputPattern(tc.pattern, tc.filename)
assert.Equal(t, tc.expected, result, "Output pattern processing should match expected result")
})
}
// Test date pattern separately - we can't deterministically test the exact output
// but we can verify it doesn't error and contains something that looks like a date
datePattern := "${date:2006-01-02}"
result := ProcessOutputPattern(datePattern, "test.txt")
assert.Regexp(t, `^\d{4}-\d{2}-\d{2}$`, result, "Date pattern should produce a date in YYYY-MM-DD format")
}
func TestCreateRcloneFilterFile(t *testing.T) {
// Test creating a filter file
pattern := "*.txt,*.csv"
// Create the filter file
filterFile, err := createRcloneFilterFile(pattern)
assert.NoError(t, err)
assert.NotEmpty(t, filterFile)
// Check that the file exists
_, err = os.Stat(filterFile)
assert.NoError(t, err)
// Clean up
defer os.Remove(filterFile)
// Read the file contents
content, err := os.ReadFile(filterFile)
assert.NoError(t, err)
// Check that the content matches the expected format
// The actual content should be two rename rules for rclone
expectedContent := "-- (.*)(\\..+)$ " + pattern + "\n" +
"-- ([^.]+)$ " + pattern + "\n"
assert.Equal(t, expectedContent, string(content))
}
func TestRunJobNow(t *testing.T) {
// Set up a temporary data directory for logs
tempDir, err := os.MkdirTemp("", "gomft-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
t.Cleanup(func() {
os.RemoveAll(tempDir)
})
// Set DATA_DIR environment variable for the test
originalDataDir := os.Getenv("DATA_DIR")
os.Setenv("DATA_DIR", tempDir)
defer os.Setenv("DATA_DIR", originalDataDir)
// Create a test database
database := setupTestDB(t)
// Create a test user
user := &db.User{
Email: "test_runjob@example.com",
IsAdmin: false,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
err = database.Create(user).Error
assert.NoError(t, err)
// Create a test config
config := &db.TransferConfig{
Name: "Test Config",
SourceType: "local",
SourcePath: "/tmp/source",
DestinationType: "local",
DestinationPath: "/tmp/dest",
CreatedBy: user.ID,
}
err = database.Create(config).Error
assert.NoError(t, err)
// Create a test job
job := &db.Job{
Name: "Test Job",
Schedule: "*/5 * * * *", // Every 5 minutes
ConfigID: config.ID,
Enabled: true,
CreatedBy: user.ID,
}
err = database.Create(job).Error
assert.NoError(t, err)
// Create a new scheduler
scheduler := New(database)
t.Cleanup(func() {
scheduler.Stop()
})
// Create a job history entry manually since the actual job execution won't work in tests
endTime := time.Now().Add(time.Second)
history := &db.JobHistory{
JobID: job.ID,
StartTime: time.Now(),
EndTime: &endTime,
Status: "completed",
FilesTransferred: 0,
BytesTransferred: 0,
ErrorMessage: "",
}
err = database.Create(history).Error
assert.NoError(t, err)
// Run the job now (this will not actually execute the job since rclone is not available in tests)
err = scheduler.RunJobNow(job.ID)
assert.NoError(t, err)
// Check that a job history entry was created
var histories []db.JobHistory
err = database.Where("job_id = ?", job.ID).Find(&histories).Error
assert.NoError(t, err)
assert.GreaterOrEqual(t, len(histories), 1)
}
func TestHasFileBeenProcessed(t *testing.T) {
// Set up a temporary data directory for logs
tempDir, err := os.MkdirTemp("", "gomft-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
t.Cleanup(func() {
os.RemoveAll(tempDir)
})
// Set DATA_DIR environment variable for the test
originalDataDir := os.Getenv("DATA_DIR")
os.Setenv("DATA_DIR", tempDir)
defer os.Setenv("DATA_DIR", originalDataDir)
// Create a test database
database := setupTestDB(t)
// Create a test user
user := &db.User{
Email: "test_fileprocessed@example.com",
IsAdmin: false,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
err = database.Create(user).Error
assert.NoError(t, err)
// Create a test config
config := &db.TransferConfig{
Name: "Test Config",
SourceType: "local",
SourcePath: "/tmp/source",
DestinationType: "local",
DestinationPath: "/tmp/dest",
CreatedBy: user.ID,
}
err = database.Create(config).Error
assert.NoError(t, err)
// Create a test job
job := &db.Job{
Name: "Test Job",
Schedule: "*/5 * * * *", // Every 5 minutes
ConfigID: config.ID,
Enabled: true,
CreatedBy: user.ID,
}
err = database.Create(job).Error
assert.NoError(t, err)
// Create a new scheduler
scheduler := New(database)
t.Cleanup(func() {
scheduler.Stop()
})
// Create a test file metadata
fileHash := "abcdef123456"
metadata := &db.FileMetadata{
JobID: job.ID,
FileName: "test.txt",
FileHash: fileHash,
FileSize: 1024,
OriginalPath: "/tmp/source/test.txt",
DestinationPath: "/tmp/dest/test.txt",
Status: "processed",
ProcessedTime: time.Now(),
}
err = database.Create(metadata).Error
assert.NoError(t, err)
// Check if the file has been processed
processed, foundMetadata, err := scheduler.hasFileBeenProcessed(job.ID, fileHash)
assert.NoError(t, err)
assert.True(t, processed)
assert.Equal(t, metadata.ID, foundMetadata.ID)
assert.Equal(t, metadata.FileName, foundMetadata.FileName)
assert.Equal(t, metadata.FileHash, foundMetadata.FileHash)
assert.Equal(t, metadata.Status, foundMetadata.Status)
// Check with a non-existent hash
processed, _, err = scheduler.hasFileBeenProcessed(job.ID, "nonexistenthash")
assert.NoError(t, err)
assert.False(t, processed)
}
func TestCheckFileProcessingHistory(t *testing.T) {
// Set up a temporary data directory for logs
tempDir, err := os.MkdirTemp("", "gomft-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
t.Cleanup(func() {
os.RemoveAll(tempDir)
})
// Set DATA_DIR environment variable for the test
originalDataDir := os.Getenv("DATA_DIR")
os.Setenv("DATA_DIR", tempDir)
defer os.Setenv("DATA_DIR", originalDataDir)
// Create a test database
database := setupTestDB(t)
// Create a test user
user := &db.User{
Email: "test_filehistory@example.com",
IsAdmin: false,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
err = database.Create(user).Error
assert.NoError(t, err)
// Create a test config
config := &db.TransferConfig{
Name: "Test Config",
SourceType: "local",
SourcePath: "/tmp/source",
DestinationType: "local",
DestinationPath: "/tmp/dest",
CreatedBy: user.ID,
}
err = database.Create(config).Error
assert.NoError(t, err)
// Create a test job
job := &db.Job{
Name: "Test Job",
Schedule: "*/5 * * * *", // Every 5 minutes
ConfigID: config.ID,
Enabled: true,
CreatedBy: user.ID,
}
err = database.Create(job).Error
assert.NoError(t, err)
// Create a new scheduler
scheduler := New(database)
t.Cleanup(func() {
scheduler.Stop()
})
// Create a test file metadata
fileName := "test.txt"
metadata := &db.FileMetadata{
JobID: job.ID,
FileName: fileName,
FileHash: "abcdef123456",
FileSize: 1024,
OriginalPath: "/tmp/source/test.txt",
DestinationPath: "/tmp/dest/test.txt",
Status: "processed",
ProcessedTime: time.Now(),
}
err = database.Create(metadata).Error
assert.NoError(t, err)
// Check file processing history
foundMetadata, err := scheduler.checkFileProcessingHistory(job.ID, fileName)
assert.NoError(t, err)
assert.Equal(t, metadata.ID, foundMetadata.ID)
assert.Equal(t, metadata.FileName, foundMetadata.FileName)
assert.Equal(t, metadata.FileHash, foundMetadata.FileHash)
assert.Equal(t, metadata.Status, foundMetadata.Status)
// Check with a non-existent file name
_, err = scheduler.checkFileProcessingHistory(job.ID, "nonexistentfile.txt")
assert.Error(t, err)
}
func TestUnscheduleJob(t *testing.T) {
// Set up a temporary data directory for logs
tempDir, err := os.MkdirTemp("", "gomft-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
t.Cleanup(func() {
os.RemoveAll(tempDir)
})
// Set DATA_DIR environment variable for the test
originalDataDir := os.Getenv("DATA_DIR")
os.Setenv("DATA_DIR", tempDir)
defer os.Setenv("DATA_DIR", originalDataDir)
// Create a test database
database := setupTestDB(t)
// Create a test user
user := &db.User{
Email: "unschedule-test@example.com",
PasswordHash: "hashed_password",
IsAdmin: true,
}
if err := database.CreateUser(user); err != nil {
t.Fatalf("Failed to create test user: %v", err)
}
// Create a test transfer config
config := &db.TransferConfig{
Name: "Unschedule Test Config",
SourceType: "local",
SourcePath: "/source",
DestinationType: "local",
DestinationPath: "/dest",
CreatedBy: user.ID,
}
if err := database.DB.Create(config).Error; err != nil {
t.Fatalf("Failed to create transfer config: %v", err)
}
// Create two test jobs
job1 := &db.Job{
Name: "Test Job 1",
Schedule: "*/15 * * * *", // Every 15 minutes
ConfigID: config.ID,
Enabled: true,
CreatedBy: user.ID,
}
if err := database.DB.Create(job1).Error; err != nil {
t.Fatalf("Failed to create job: %v", err)
}
job2 := &db.Job{
Name: "Test Job 2",
Schedule: "0 */2 * * *", // Every 2 hours
ConfigID: config.ID,
Enabled: true,
CreatedBy: user.ID,
}
if err := database.DB.Create(job2).Error; err != nil {
t.Fatalf("Failed to create job: %v", err)
}
// Create a new scheduler
scheduler := New(database)
t.Cleanup(func() {
scheduler.Stop()
})
// Schedule both jobs
if err := scheduler.ScheduleJob(job1); err != nil {
t.Fatalf("Failed to schedule job1: %v", err)
}
if err := scheduler.ScheduleJob(job2); err != nil {
t.Fatalf("Failed to schedule job2: %v", err)
}
// Verify both jobs are scheduled
scheduler.jobMutex.Lock()
_, job1Exists := scheduler.jobs[job1.ID]
_, job2Exists := scheduler.jobs[job2.ID]
scheduler.jobMutex.Unlock()
assert.True(t, job1Exists, "Expected job1 to be scheduled")
assert.True(t, job2Exists, "Expected job2 to be scheduled")
// Unschedule job1
scheduler.UnscheduleJob(job1.ID)
// Verify job1 is unscheduled but job2 is still scheduled
scheduler.jobMutex.Lock()
_, job1ExistsAfter := scheduler.jobs[job1.ID]
_, job2ExistsAfter := scheduler.jobs[job2.ID]
scheduler.jobMutex.Unlock()
assert.False(t, job1ExistsAfter, "Expected job1 to be unscheduled")
assert.True(t, job2ExistsAfter, "Expected job2 to still be scheduled")
// Unschedule a non-existent job (shouldn't cause any issues)
scheduler.UnscheduleJob(9999)
// Verify job2 is still scheduled after attempting to unschedule non-existent job
scheduler.jobMutex.Lock()
_, job2StillExists := scheduler.jobs[job2.ID]
scheduler.jobMutex.Unlock()
assert.True(t, job2StillExists, "Expected job2 to still be scheduled after unscheduling non-existent job")
}
func TestRotateLogs(t *testing.T) {
// Set up a temporary data directory for logs
tempDir, err := os.MkdirTemp("", "gomft-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
t.Cleanup(func() {
os.RemoveAll(tempDir)
})
// Set DATA_DIR environment variable for the test
originalDataDir := os.Getenv("DATA_DIR")
os.Setenv("DATA_DIR", tempDir)
defer os.Setenv("DATA_DIR", originalDataDir)
// Create log directory
logDir := filepath.Join(tempDir, "logs")
err = os.MkdirAll(logDir, 0755)
if err != nil {
t.Fatalf("Failed to create log directory: %v", err)
}
// Create a test database
database := setupTestDB(t)
// Create a new scheduler
scheduler := New(database)
t.Cleanup(func() {
scheduler.Stop()
})
// Write some logs to ensure there's content
for i := 0; i < 10; i++ {
scheduler.log.LogInfo("Test log message %d", i)
scheduler.log.LogError("Test error message %d", i)
scheduler.log.LogDebug("Test debug message %d", i)
}
// Verify the log file exists
logFiles, err := os.ReadDir(logDir)
if err != nil {
t.Fatalf("Failed to read log directory: %v", err)
}
if len(logFiles) == 0 {
t.Fatalf("Expected log files to be created, but none found in %s", logDir)
}
// Rotate logs
err = scheduler.RotateLogs()
assert.NoError(t, err, "Expected no error when rotating logs")
// Force flush by writing more logs
for i := 0; i < 5; i++ {
scheduler.log.LogInfo("Post-rotation log message %d", i)
}
// Check that log files still exist
logFilesAfter, err := os.ReadDir(logDir)
if err != nil {
t.Fatalf("Failed to read log directory after rotation: %v", err)
}
assert.GreaterOrEqual(t, len(logFilesAfter), len(logFiles),
"Expected at least the same number of log files after rotation")
}
func TestLoadJobs(t *testing.T) {
// Set up a temporary data directory for logs
tempDir, err := os.MkdirTemp("", "gomft-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
t.Cleanup(func() {
os.RemoveAll(tempDir)
})
// Set DATA_DIR environment variable for the test
originalDataDir := os.Getenv("DATA_DIR")
os.Setenv("DATA_DIR", tempDir)
defer os.Setenv("DATA_DIR", originalDataDir)
// Create a test database
database := setupTestDB(t)
// Create a test user
user := &db.User{
Email: "loadjobs-test@example.com",
PasswordHash: "hashed_password",
IsAdmin: true,
}
if err := database.CreateUser(user); err != nil {
t.Fatalf("Failed to create test user: %v", err)
}
// Create a test transfer config
config := &db.TransferConfig{
Name: "Load Jobs Test Config",
SourceType: "local",
SourcePath: "/source",
DestinationType: "local",
DestinationPath: "/dest",
CreatedBy: user.ID,
}
if err := database.DB.Create(config).Error; err != nil {
t.Fatalf("Failed to create transfer config: %v", err)
}
// Create multiple jobs with different states (enabled/disabled)
jobs := []db.Job{
{
Name: "Enabled Job 1",
Schedule: "*/10 * * * *", // Every 10 minutes
ConfigID: config.ID,
Enabled: true,
CreatedBy: user.ID,
},
{
Name: "Enabled Job 2",
Schedule: "0 */1 * * *", // Every hour
ConfigID: config.ID,
Enabled: true,
CreatedBy: user.ID,
},
{
Name: "Disabled Job",
Schedule: "0 0 * * *", // Daily at midnight
ConfigID: config.ID,
Enabled: false,
CreatedBy: user.ID,
},
}
// Create jobs in the database
for i := range jobs {
if err := database.DB.Create(&jobs[i]).Error; err != nil {
t.Fatalf("Failed to create job: %v", err)
}
}
// Create a new scheduler, which should load the jobs
scheduler := New(database)
t.Cleanup(func() {
scheduler.Stop()
})
// Verify that only the enabled jobs were scheduled
scheduler.jobMutex.Lock()
defer scheduler.jobMutex.Unlock()
// Should have 2 enabled jobs loaded
assert.Equal(t, 2, len(scheduler.jobs), "Expected 2 jobs to be loaded (only the enabled ones)")
// Check enabled jobs are scheduled
_, job1Exists := scheduler.jobs[jobs[0].ID]
_, job2Exists := scheduler.jobs[jobs[1].ID]
_, job3Exists := scheduler.jobs[jobs[2].ID]
assert.True(t, job1Exists, "Expected enabled job 1 to be scheduled")
assert.True(t, job2Exists, "Expected enabled job 2 to be scheduled")
assert.False(t, job3Exists, "Expected disabled job to not be scheduled")
// Test with an error in GetActiveJobs (by using a new DB instance with no connection)
closedDB := &db.DB{DB: nil}
errorScheduler := &Scheduler{
cron: cron.New(),
db: closedDB,
jobMutex: sync.Mutex{},
jobs: make(map[uint]cron.EntryID),
log: NewLogger(),
}
errorScheduler.loadJobs() // This should not panic even if DB access fails
// Cleanup
errorScheduler.Stop()
}
func TestStopScheduler(t *testing.T) {
// Set up a temporary data directory for logs
tempDir, err := os.MkdirTemp("", "gomft-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
t.Cleanup(func() {
os.RemoveAll(tempDir)
})
// Set DATA_DIR environment variable for the test
originalDataDir := os.Getenv("DATA_DIR")
os.Setenv("DATA_DIR", tempDir)
defer os.Setenv("DATA_DIR", originalDataDir)
// Create a test database
database := setupTestDB(t)
// Create a test user
user := &db.User{
Email: "stop-test@example.com",
PasswordHash: "hashed_password",
IsAdmin: true,
}
if err := database.CreateUser(user); err != nil {
t.Fatalf("Failed to create test user: %v", err)
}
// Create a test transfer config
config := &db.TransferConfig{
Name: "Stop Test Config",
SourceType: "local",
SourcePath: "/source",
DestinationType: "local",
DestinationPath: "/dest",
CreatedBy: user.ID,
}
if err := database.DB.Create(config).Error; err != nil {
t.Fatalf("Failed to create transfer config: %v", err)
}
// Create a test job with a frequent schedule
job := &db.Job{
Name: "Test Job",
Schedule: "*/1 * * * *", // Every minute
ConfigID: config.ID,
Enabled: true,
CreatedBy: user.ID,
}
if err := database.DB.Create(job).Error; err != nil {
t.Fatalf("Failed to create job: %v", err)
}
// Create a new scheduler
scheduler := New(database)
// Schedule the job
if err := scheduler.ScheduleJob(job); err != nil {
t.Fatalf("Failed to schedule job: %v", err)
}
// Verify job is scheduled
scheduler.jobMutex.Lock()
_, jobExists := scheduler.jobs[job.ID]
scheduler.jobMutex.Unlock()
assert.True(t, jobExists, "Expected job to be scheduled")
// Stop the scheduler
scheduler.Stop()
// Verify the scheduler is stopped (testing this is tricky since it's internal state)
// We can't directly test the cron.Cron state, but we can test that resources are released
// by creating a new scheduler with the same database and verifying it loads jobs correctly
// Create a new scheduler
newScheduler := New(database)
t.Cleanup(func() {
newScheduler.Stop()
})
// Verify the new scheduler loads the job correctly
newScheduler.jobMutex.Lock()
_, jobExistsInNew := newScheduler.jobs[job.ID]
newScheduler.jobMutex.Unlock()
assert.True(t, jobExistsInNew, "Expected job to be loaded in new scheduler")
}
func TestFileProcessingFullCycle(t *testing.T) {
// Set up a temporary data directory for logs
tempDir, err := os.MkdirTemp("", "gomft-test-*")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
}
t.Cleanup(func() {
os.RemoveAll(tempDir)
})
// Set DATA_DIR environment variable for the test
originalDataDir := os.Getenv("DATA_DIR")
os.Setenv("DATA_DIR", tempDir)
defer os.Setenv("DATA_DIR", originalDataDir)
// Create a test database
database := setupTestDB(t)
// Create a test user
user := &db.User{
Email: "file-processing-test@example.com",
PasswordHash: "hashed_password",
IsAdmin: true,
}
if err := database.CreateUser(user); err != nil {
t.Fatalf("Failed to create test user: %v", err)
}
// Create a test transfer config
config := &db.TransferConfig{
Name: "File Processing Test Config",
SourceType: "local",
SourcePath: "/source",
DestinationType: "local",
DestinationPath: "/dest",
SkipProcessedFiles: true, // Instead of DuplicatePolicy
CreatedBy: user.ID,
}
if err := database.DB.Create(config).Error; err != nil {
t.Fatalf("Failed to create transfer config: %v", err)
}
// Create a test job
job := &db.Job{
Name: "File Processing Test Job",
Schedule: "*/30 * * * *", // Every 30 minutes
ConfigID: config.ID,
Enabled: true,
CreatedBy: user.ID,
}
if err := database.DB.Create(job).Error; err != nil {
t.Fatalf("Failed to create job: %v", err)
}
// Create a scheduler
scheduler := New(database)
t.Cleanup(func() {
scheduler.Stop()
})
// Test scenario 1: File doesn't exist in history yet
fileName := "new_file.txt"
metadata, err := scheduler.checkFileProcessingHistory(job.ID, fileName)
assert.Error(t, err, "Should return error when file not in history")
assert.Nil(t, metadata, "Metadata should be nil when file not found")
// Create a file metadata record
fileMetadata := &db.FileMetadata{
JobID: job.ID,
FileName: fileName,
OriginalPath: "/source/" + fileName,
FileSize: 1024,
FileHash: "test_hash_123",
DestinationPath: "/dest/processed_" + fileName,
Status: "success",
CreationTime: time.Now().Add(-30 * time.Minute),
ModTime: time.Now().Add(-30 * time.Minute),
ProcessedTime: time.Now().Add(-15 * time.Minute),
}
if err := database.DB.Create(fileMetadata).Error; err != nil {
t.Fatalf("Failed to create file metadata: %v", err)
}
// Test scenario 2: File exists in history
metadata, err = scheduler.checkFileProcessingHistory(job.ID, fileName)
assert.NoError(t, err, "Should not return error when file found in history")
assert.NotNil(t, metadata, "Should find metadata for file")
assert.Equal(t, fileName, metadata.FileName, "Filename should match")
assert.Equal(t, "/dest/processed_"+fileName, metadata.DestinationPath, "Destination path should match")
// Test scenario 3: Check using file hash
hasProcessed, metadata, err := scheduler.hasFileBeenProcessed(job.ID, "test_hash_123")
assert.NoError(t, err, "Should not return error when checking by hash")
assert.True(t, hasProcessed, "Should identify file as processed")
assert.NotNil(t, metadata, "Should return metadata when file found by hash")
// Test scenario 4: Check with empty hash (should always return false)
hasProcessed, metadata, err = scheduler.hasFileBeenProcessed(job.ID, "")
assert.NoError(t, err, "Should not return error with empty hash")
assert.False(t, hasProcessed, "Should return false for empty hash")
assert.Nil(t, metadata, "Should not return metadata for empty hash")
// Test scenario 5: Check with non-existent hash
hasProcessed, metadata, err = scheduler.hasFileBeenProcessed(job.ID, "non_existent_hash")
assert.NoError(t, err, "Should not return error for non-existent hash")
assert.False(t, hasProcessed, "Should return false for non-existent hash")
assert.Nil(t, metadata, "Should not return metadata for non-existent hash")
}