mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-08 15:41:20 +02:00
- Update .gitignore to include new provider files and ensure proper tracking. - Refactor job and config forms to support multiple configuration selections, improving user experience. - Implement logic to handle the initialization of skipProcessedFiles with a default value. - Add new provider form templates for better organization and management of source and destination configurations. - Enhance tests for provider forms and job configurations to ensure robust functionality. - Introduce nullable handling for skipProcessedFiles in the database schema and update related migrations. - Improve error handling and validation in job creation and editing processes.
1090 lines
32 KiB
Go
1090 lines
32 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/starfleetcptn/gomft/internal/db"
|
|
"github.com/starfleetcptn/gomft/internal/scheduler"
|
|
"github.com/starfleetcptn/gomft/internal/testutils"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// setupJobsTest prepares test environment with database, mock scheduler, and handlers
|
|
func setupJobsTest(t *testing.T) (*Handlers, *gin.Engine, *db.DB, *db.User, *db.TransferConfig) {
|
|
// Set up test database
|
|
database := testutils.SetupTestDB(t)
|
|
|
|
// Create test user
|
|
user := &db.User{
|
|
Email: "jobtest@example.com",
|
|
PasswordHash: "hashedpassword",
|
|
IsAdmin: false,
|
|
LastPasswordChange: time.Now(),
|
|
}
|
|
database.Create(user)
|
|
|
|
// Create admin user
|
|
adminUser := &db.User{
|
|
Email: "jobadmin@example.com",
|
|
PasswordHash: "hashedpassword",
|
|
IsAdmin: true,
|
|
LastPasswordChange: time.Now(),
|
|
}
|
|
database.Create(adminUser)
|
|
|
|
// Create test config
|
|
config := &db.TransferConfig{
|
|
Name: "Test Config",
|
|
SourceType: "local",
|
|
SourcePath: "/source",
|
|
DestinationType: "local",
|
|
DestinationPath: "/dest",
|
|
CreatedBy: user.ID,
|
|
}
|
|
database.Create(config)
|
|
|
|
// Create mock scheduler
|
|
mockScheduler := scheduler.NewMockScheduler()
|
|
|
|
// Set up Gin router
|
|
gin.SetMode(gin.TestMode)
|
|
router := gin.New()
|
|
|
|
// Create handlers
|
|
handlers := &Handlers{
|
|
DB: database,
|
|
JWTSecret: "test-jwt-secret",
|
|
Scheduler: mockScheduler,
|
|
}
|
|
|
|
// Set up auth middleware for testing
|
|
router.Use(func(c *gin.Context) {
|
|
c.Set("userID", user.ID)
|
|
c.Set("email", user.Email)
|
|
c.Set("isAdmin", false)
|
|
c.Next()
|
|
})
|
|
|
|
return handlers, router, database, user, config
|
|
}
|
|
|
|
// setupAdminJobsTest prepares test environment with admin user permissions
|
|
func setupAdminJobsTest(t *testing.T) (*Handlers, *gin.Engine, *db.DB, *db.User, *db.TransferConfig) {
|
|
handlers, router, database, user, config := setupJobsTest(t)
|
|
|
|
// Replace middleware with admin permissions
|
|
router.Use(func(c *gin.Context) {
|
|
c.Set("userID", user.ID)
|
|
c.Set("email", user.Email)
|
|
c.Set("isAdmin", true)
|
|
c.Next()
|
|
})
|
|
|
|
return handlers, router, database, user, config
|
|
}
|
|
|
|
func TestHandleJobs(t *testing.T) {
|
|
// Setup test environment
|
|
handlers, router, database, user, _ := setupJobsTest(t)
|
|
|
|
// Create test jobs
|
|
job1 := &db.Job{
|
|
Name: "Test Job 1",
|
|
Schedule: "*/5 * * * *",
|
|
ConfigID: 1,
|
|
Enabled: true,
|
|
CreatedBy: user.ID,
|
|
}
|
|
database.Create(job1)
|
|
|
|
job2 := &db.Job{
|
|
Name: "Test Job 2",
|
|
Schedule: "*/10 * * * *",
|
|
ConfigID: 1,
|
|
Enabled: false,
|
|
CreatedBy: user.ID,
|
|
}
|
|
database.Create(job2)
|
|
|
|
// Create job for another user
|
|
otherUser := &db.User{
|
|
Email: "other@example.com",
|
|
PasswordHash: "hashedpassword",
|
|
IsAdmin: false,
|
|
LastPasswordChange: time.Now(),
|
|
}
|
|
database.Create(otherUser)
|
|
|
|
otherJob := &db.Job{
|
|
Name: "Other User Job",
|
|
Schedule: "*/15 * * * *",
|
|
ConfigID: 1,
|
|
Enabled: true,
|
|
CreatedBy: otherUser.ID,
|
|
}
|
|
database.Create(otherJob)
|
|
|
|
// Add route
|
|
router.GET("/jobs", handlers.HandleJobs)
|
|
|
|
// Create request
|
|
req, _ := http.NewRequest(http.MethodGet, "/jobs", nil)
|
|
resp := httptest.NewRecorder()
|
|
|
|
// Serve request
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response
|
|
assert.Equal(t, http.StatusOK, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "Test Job 1")
|
|
assert.Contains(t, resp.Body.String(), "Test Job 2")
|
|
assert.NotContains(t, resp.Body.String(), "Other User Job") // Should not contain other user's job
|
|
}
|
|
|
|
func TestHandleNewJob(t *testing.T) {
|
|
// Setup test environment
|
|
handlers, router, _, _, _ := setupJobsTest(t)
|
|
|
|
// Add route
|
|
router.GET("/jobs/new", handlers.HandleNewJob)
|
|
|
|
// Create request
|
|
req, _ := http.NewRequest(http.MethodGet, "/jobs/new", nil)
|
|
resp := httptest.NewRecorder()
|
|
|
|
// Serve request
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response
|
|
assert.Equal(t, http.StatusOK, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "Create New Job")
|
|
assert.Contains(t, resp.Body.String(), "Schedule")
|
|
assert.Contains(t, resp.Body.String(), "Test Config") // Should contain config name
|
|
}
|
|
|
|
func TestHandleEditJob(t *testing.T) {
|
|
// Setup test environment
|
|
handlers, router, database, user, config := setupJobsTest(t)
|
|
|
|
// Create test job
|
|
job := &db.Job{
|
|
Name: "Test Job",
|
|
Schedule: "*/5 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: true,
|
|
CreatedBy: user.ID,
|
|
}
|
|
database.Create(job)
|
|
|
|
// Create job for another user
|
|
otherUser := &db.User{
|
|
Email: "other@example.com",
|
|
PasswordHash: "hashedpassword",
|
|
IsAdmin: false,
|
|
LastPasswordChange: time.Now(),
|
|
}
|
|
database.Create(otherUser)
|
|
|
|
otherJob := &db.Job{
|
|
Name: "Other User Job",
|
|
Schedule: "*/15 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: true,
|
|
CreatedBy: otherUser.ID,
|
|
}
|
|
database.Create(otherJob)
|
|
|
|
// Add routes
|
|
router.GET("/jobs/:id/edit", handlers.HandleEditJob)
|
|
|
|
// Test case 1: Edit own job
|
|
req, _ := http.NewRequest(http.MethodGet, "/jobs/"+strconv.Itoa(int(job.ID))+"/edit", nil)
|
|
resp := httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response
|
|
assert.Equal(t, http.StatusOK, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "Edit Job")
|
|
assert.Contains(t, resp.Body.String(), "Test Job")
|
|
|
|
// Test case 2: Try to edit another user's job (should redirect)
|
|
req, _ = http.NewRequest(http.MethodGet, "/jobs/"+strconv.Itoa(int(otherJob.ID))+"/edit", nil)
|
|
resp = httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Should redirect to jobs page
|
|
assert.Equal(t, http.StatusFound, resp.Code)
|
|
assert.Equal(t, "/jobs", resp.Header().Get("Location"))
|
|
|
|
// Test case 3: Admin can edit any job
|
|
adminHandlers, adminRouter, _, _, _ := setupAdminJobsTest(t)
|
|
adminRouter.GET("/jobs/:id/edit", adminHandlers.HandleEditJob)
|
|
|
|
// Create the job again in the admin test environment
|
|
adminOtherJob := &db.Job{
|
|
Name: "Other User Job",
|
|
Schedule: "*/15 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: true,
|
|
CreatedBy: otherUser.ID,
|
|
}
|
|
database.Create(adminOtherJob)
|
|
|
|
req, _ = http.NewRequest(http.MethodGet, "/jobs/"+strconv.Itoa(int(adminOtherJob.ID))+"/edit", nil)
|
|
resp = httptest.NewRecorder()
|
|
adminRouter.ServeHTTP(resp, req)
|
|
|
|
// Should allow access
|
|
assert.Equal(t, http.StatusOK, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "Other User Job")
|
|
}
|
|
|
|
func TestHandleCreateJob(t *testing.T) {
|
|
// Setup test environment
|
|
handlers, router, database, user, config := setupJobsTest(t)
|
|
|
|
// Clean up any existing jobs for this test user first to ensure a clean state
|
|
database.Where("created_by = ?", user.ID).Delete(&db.Job{})
|
|
|
|
// Add route
|
|
router.POST("/jobs", handlers.HandleCreateJob)
|
|
|
|
// Create form data with a unique job name to avoid conflicts
|
|
jobName := "New Test Job " + time.Now().Format("20060102150405")
|
|
formData := url.Values{
|
|
"name": {jobName},
|
|
"schedule": {"*/15 * * * *"},
|
|
"config_id": {strconv.Itoa(int(config.ID))},
|
|
"config_ids[]": {strconv.Itoa(int(config.ID))},
|
|
"enabled": {"true"},
|
|
}
|
|
|
|
// Create request
|
|
req, _ := http.NewRequest(http.MethodPost, "/jobs", strings.NewReader(formData.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
resp := httptest.NewRecorder()
|
|
|
|
// Serve request
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response - should redirect to jobs list
|
|
assert.Equal(t, http.StatusFound, resp.Code)
|
|
assert.Equal(t, "/jobs", resp.Header().Get("Location"))
|
|
|
|
// Verify job was created with a specific query matching exactly what we created
|
|
var job db.Job
|
|
result := database.Where("created_by = ? AND name = ?", user.ID, jobName).First(&job)
|
|
assert.NoError(t, result.Error, "Should find the newly created job")
|
|
|
|
// Verify job properties
|
|
assert.Equal(t, jobName, job.Name)
|
|
assert.Equal(t, "*/15 * * * *", job.Schedule)
|
|
assert.Equal(t, config.ID, job.ConfigID)
|
|
assert.True(t, job.Enabled)
|
|
|
|
// Test case 2: Try to use another user's config
|
|
otherUser := &db.User{
|
|
Email: "other@example.com",
|
|
PasswordHash: "hashedpassword",
|
|
IsAdmin: false,
|
|
LastPasswordChange: time.Now(),
|
|
}
|
|
database.Create(otherUser)
|
|
|
|
otherConfig := &db.TransferConfig{
|
|
Name: "Other User Config",
|
|
SourceType: "local",
|
|
SourcePath: "/source2",
|
|
DestinationType: "local",
|
|
DestinationPath: "/dest2",
|
|
CreatedBy: otherUser.ID,
|
|
}
|
|
database.Create(otherConfig)
|
|
|
|
// Create a new form with both config_id and config_ids[] for the other user's config
|
|
formData = url.Values{
|
|
"name": {"Unauthorized Job"},
|
|
"schedule": {"*/30 * * * *"},
|
|
"config_id": {strconv.Itoa(int(otherConfig.ID))},
|
|
"config_ids[]": {strconv.Itoa(int(otherConfig.ID))},
|
|
"enabled": {"true"},
|
|
}
|
|
|
|
// Create request
|
|
req, _ = http.NewRequest(http.MethodPost, "/jobs", strings.NewReader(formData.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
resp = httptest.NewRecorder()
|
|
|
|
// Serve request
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Debug info
|
|
t.Logf("Response code: %d", resp.Code)
|
|
t.Logf("Response body: %s", resp.Body.String())
|
|
|
|
// Should return forbidden
|
|
assert.Equal(t, http.StatusForbidden, resp.Code, "Should get 403 Forbidden when trying to use another user's config")
|
|
assert.Contains(t, resp.Body.String(), "You do not have permission")
|
|
}
|
|
|
|
func TestHandleCreateJobWithMultipleConfigs(t *testing.T) {
|
|
// Setup test environment
|
|
handlers, router, database, user, config := setupJobsTest(t)
|
|
|
|
// Create another config for the same user
|
|
config2 := &db.TransferConfig{
|
|
Name: "Test Config 2",
|
|
SourceType: "local",
|
|
SourcePath: "/source2",
|
|
DestinationType: "local",
|
|
DestinationPath: "/dest2",
|
|
CreatedBy: user.ID,
|
|
}
|
|
database.Create(config2)
|
|
|
|
// Add route
|
|
router.POST("/jobs", handlers.HandleCreateJob)
|
|
|
|
// Create form data with multiple configs
|
|
formData := url.Values{
|
|
"name": {"Multi-Config Job"},
|
|
"schedule": {"*/15 * * * *"},
|
|
"config_ids[]": {
|
|
strconv.Itoa(int(config.ID)),
|
|
strconv.Itoa(int(config2.ID)),
|
|
},
|
|
"enabled": {"true"},
|
|
}
|
|
|
|
// Create request
|
|
req, _ := http.NewRequest(http.MethodPost, "/jobs", strings.NewReader(formData.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
resp := httptest.NewRecorder()
|
|
|
|
// Serve request
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response - should redirect to jobs list
|
|
assert.Equal(t, http.StatusFound, resp.Code)
|
|
assert.Equal(t, "/jobs", resp.Header().Get("Location"))
|
|
|
|
// Verify job was created with multiple configs
|
|
var jobs []db.Job
|
|
database.Where("created_by = ?", user.ID).Find(&jobs)
|
|
|
|
// Find the job we just created
|
|
var multiConfigJob *db.Job
|
|
for _, job := range jobs {
|
|
if job.Name == "Multi-Config Job" {
|
|
multiConfigJob = &job
|
|
break
|
|
}
|
|
}
|
|
|
|
assert.NotNil(t, multiConfigJob, "Multi-config job should have been created")
|
|
if multiConfigJob != nil {
|
|
// Verify primary ConfigID is set to first config
|
|
assert.Equal(t, config.ID, multiConfigJob.ConfigID)
|
|
|
|
// Check ConfigIDs contains both IDs
|
|
configIDs := multiConfigJob.GetConfigIDsList()
|
|
assert.Len(t, configIDs, 2)
|
|
assert.Contains(t, configIDs, config.ID)
|
|
assert.Contains(t, configIDs, config2.ID)
|
|
|
|
// Check that we can get configs for the job
|
|
configs, err := handlers.DB.GetConfigsForJob(multiConfigJob.ID)
|
|
assert.NoError(t, err)
|
|
assert.Len(t, configs, 2)
|
|
}
|
|
}
|
|
|
|
func TestHandleUpdateJob(t *testing.T) {
|
|
// Setup test environment
|
|
handlers, router, database, user, config := setupJobsTest(t)
|
|
|
|
// Clean up any existing jobs for this test user first to ensure a clean state
|
|
result := database.Where("created_by = ?", user.ID).Delete(&db.Job{})
|
|
assert.NoError(t, result.Error, "Failed to clean up existing jobs")
|
|
|
|
// Create test job with a unique name
|
|
jobName := "Test Job " + time.Now().Format("20060102150405")
|
|
job := &db.Job{
|
|
Name: jobName,
|
|
Schedule: "*/5 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: true,
|
|
CreatedBy: user.ID,
|
|
}
|
|
|
|
// Set the config list to include the config ID - this is critical
|
|
job.SetConfigIDsList([]uint{config.ID})
|
|
result = database.Create(job)
|
|
assert.NoError(t, result.Error, "Failed to create test job")
|
|
|
|
// Verify the job was created successfully
|
|
var createdJob db.Job
|
|
err := database.First(&createdJob, job.ID).Error
|
|
assert.NoError(t, err, "Should find the newly created job")
|
|
assert.Equal(t, jobName, createdJob.Name, "Created job should have the expected name")
|
|
assert.Equal(t, "*/5 * * * *", createdJob.Schedule, "Created job should have the expected schedule")
|
|
assert.True(t, createdJob.Enabled, "Created job should be enabled")
|
|
|
|
// Add route
|
|
router.PUT("/jobs/:id", handlers.HandleUpdateJob)
|
|
|
|
// Create form data for update with a unique updated name
|
|
updatedName := "Updated Job " + time.Now().Format("20060102150405")
|
|
|
|
// Include both config_id and config_ids[] parameters in the correct format
|
|
formData := url.Values{
|
|
"name": {updatedName},
|
|
"schedule": {"0 0 * * *"},
|
|
"config_id": {strconv.Itoa(int(config.ID))},
|
|
"config_ids[]": {strconv.Itoa(int(config.ID))},
|
|
"enabled": {"false"},
|
|
}
|
|
|
|
// Create request
|
|
req, _ := http.NewRequest(http.MethodPut, "/jobs/"+strconv.Itoa(int(job.ID)), strings.NewReader(formData.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
resp := httptest.NewRecorder()
|
|
|
|
// Serve request
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Debug info
|
|
t.Logf("Update response code: %d", resp.Code)
|
|
t.Logf("Update response body: %s", resp.Body.String())
|
|
|
|
// Check response - should redirect to jobs list
|
|
assert.Equal(t, http.StatusFound, resp.Code, "Response should redirect to jobs list")
|
|
assert.Equal(t, "/jobs", resp.Header().Get("Location"), "Should redirect to /jobs")
|
|
|
|
// Verify job was updated
|
|
var updatedJob db.Job
|
|
err = database.First(&updatedJob, job.ID).Error
|
|
assert.NoError(t, err, "Should be able to find the job after update")
|
|
|
|
// Print values for debugging
|
|
t.Logf("Initial job: name=%s, schedule=%s, enabled=%v",
|
|
jobName, "*/5 * * * *", true)
|
|
t.Logf("Updated job in DB: name=%s, schedule=%s, enabled=%v",
|
|
updatedJob.Name, updatedJob.Schedule, updatedJob.Enabled)
|
|
|
|
// Verify individual fields one by one
|
|
assert.Equal(t, updatedName, updatedJob.Name, "Job name should be updated")
|
|
assert.Equal(t, "0 0 * * *", updatedJob.Schedule, "Job schedule should be updated")
|
|
assert.False(t, updatedJob.Enabled, "Enabled status should be false")
|
|
|
|
// Make sure the ConfigIDs are still correct
|
|
configIDs := updatedJob.GetConfigIDsList()
|
|
assert.Len(t, configIDs, 1, "Should have 1 config ID")
|
|
assert.Contains(t, configIDs, config.ID, "Should contain the original config ID")
|
|
|
|
// Test case 2: Try to update another user's job
|
|
otherUser := &db.User{
|
|
Email: "other@example.com",
|
|
PasswordHash: "hashedpassword",
|
|
IsAdmin: false,
|
|
LastPasswordChange: time.Now(),
|
|
}
|
|
result = database.Create(otherUser)
|
|
assert.NoError(t, result.Error, "Should create other user successfully")
|
|
|
|
// Create a job for another user
|
|
otherJob := &db.Job{
|
|
Name: "Other User Job " + time.Now().Format("20060102150405"),
|
|
Schedule: "*/15 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: true,
|
|
CreatedBy: otherUser.ID,
|
|
}
|
|
// Make sure the other job also has a config list set
|
|
otherJob.SetConfigIDsList([]uint{config.ID})
|
|
result = database.Create(otherJob)
|
|
assert.NoError(t, result.Error, "Should create other user's job successfully")
|
|
|
|
// Try to update another user's job
|
|
req, _ = http.NewRequest(http.MethodPut, "/jobs/"+strconv.Itoa(int(otherJob.ID)), strings.NewReader(formData.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
resp = httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Debug info
|
|
t.Logf("Unauthorized update response code: %d", resp.Code)
|
|
t.Logf("Unauthorized update response body: %s", resp.Body.String())
|
|
|
|
// Should return forbidden
|
|
assert.Equal(t, http.StatusForbidden, resp.Code, "Should get 403 Forbidden when updating another user's job")
|
|
assert.Contains(t, resp.Body.String(), "You do not have permission")
|
|
}
|
|
|
|
func TestHandleUpdateJobWithMultipleConfigs(t *testing.T) {
|
|
// Setup test environment
|
|
handlers, router, database, user, config := setupJobsTest(t)
|
|
|
|
// Create two additional configs
|
|
config2 := &db.TransferConfig{
|
|
Name: "Update Test Config 2",
|
|
SourceType: "local",
|
|
SourcePath: "/source2",
|
|
DestinationType: "local",
|
|
DestinationPath: "/dest2",
|
|
CreatedBy: user.ID,
|
|
}
|
|
database.Create(config2)
|
|
|
|
config3 := &db.TransferConfig{
|
|
Name: "Update Test Config 3",
|
|
SourceType: "local",
|
|
SourcePath: "/source3",
|
|
DestinationType: "local",
|
|
DestinationPath: "/dest3",
|
|
CreatedBy: user.ID,
|
|
}
|
|
database.Create(config3)
|
|
|
|
// Create a test job
|
|
job := &db.Job{
|
|
Name: "Test Job for Multi-config Update",
|
|
Schedule: "*/5 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: true,
|
|
CreatedBy: user.ID,
|
|
}
|
|
// Set initial configs (just config1)
|
|
job.SetConfigIDsList([]uint{config.ID})
|
|
database.Create(job)
|
|
|
|
// Add route
|
|
router.PUT("/jobs/:id", handlers.HandleUpdateJob)
|
|
|
|
// Create form data with multiple configs
|
|
formData := url.Values{
|
|
"name": {"Updated Multi-Config Job"},
|
|
"schedule": {"0 * * * *"},
|
|
"config_ids[]": {
|
|
strconv.Itoa(int(config2.ID)),
|
|
strconv.Itoa(int(config3.ID)),
|
|
},
|
|
"enabled": {"true"},
|
|
}
|
|
|
|
// Create request
|
|
req, _ := http.NewRequest(http.MethodPut, "/jobs/"+strconv.Itoa(int(job.ID)), strings.NewReader(formData.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
resp := httptest.NewRecorder()
|
|
|
|
// Serve request
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response - should redirect to jobs list
|
|
assert.Equal(t, http.StatusFound, resp.Code)
|
|
assert.Equal(t, "/jobs", resp.Header().Get("Location"))
|
|
|
|
// Verify job was updated with new configs
|
|
var updatedJob db.Job
|
|
database.First(&updatedJob, job.ID)
|
|
|
|
assert.Equal(t, "Updated Multi-Config Job", updatedJob.Name)
|
|
assert.Equal(t, "0 * * * *", updatedJob.Schedule)
|
|
assert.True(t, updatedJob.Enabled)
|
|
|
|
// The primary ConfigID should be updated to the first config in the new list
|
|
assert.Equal(t, config2.ID, updatedJob.ConfigID)
|
|
|
|
// Check ConfigIDs contains the new IDs
|
|
configIDs := updatedJob.GetConfigIDsList()
|
|
assert.Len(t, configIDs, 2)
|
|
assert.Contains(t, configIDs, config2.ID)
|
|
assert.Contains(t, configIDs, config3.ID)
|
|
assert.NotContains(t, configIDs, config.ID) // Original config should be gone
|
|
|
|
// Check that we can get configs for the job
|
|
configs, err := handlers.DB.GetConfigsForJob(updatedJob.ID)
|
|
assert.NoError(t, err)
|
|
assert.Len(t, configs, 2)
|
|
}
|
|
|
|
func TestHandleDeleteJob(t *testing.T) {
|
|
// Setup test environment
|
|
handlers, router, database, user, config := setupJobsTest(t)
|
|
|
|
// Create test job
|
|
job := &db.Job{
|
|
Name: "Test Job",
|
|
Schedule: "*/5 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: true,
|
|
CreatedBy: user.ID,
|
|
}
|
|
database.Create(job)
|
|
|
|
// Add route
|
|
router.DELETE("/jobs/:id", handlers.HandleDeleteJob)
|
|
|
|
// Create request
|
|
req, _ := http.NewRequest(http.MethodDelete, "/jobs/"+strconv.Itoa(int(job.ID)), nil)
|
|
resp := httptest.NewRecorder()
|
|
|
|
// Serve request
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response
|
|
assert.Equal(t, http.StatusOK, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "Job deleted successfully")
|
|
|
|
// Verify job was deleted
|
|
var deletedJob db.Job
|
|
result := database.First(&deletedJob, job.ID)
|
|
assert.Error(t, result.Error) // Should not find the job
|
|
|
|
// Test case 2: Try to delete another user's job
|
|
otherUser := &db.User{
|
|
Email: "other@example.com",
|
|
PasswordHash: "hashedpassword",
|
|
IsAdmin: false,
|
|
LastPasswordChange: time.Now(),
|
|
}
|
|
database.Create(otherUser)
|
|
|
|
otherJob := &db.Job{
|
|
Name: "Other User Job",
|
|
Schedule: "*/15 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: true,
|
|
CreatedBy: otherUser.ID,
|
|
}
|
|
database.Create(otherJob)
|
|
|
|
req, _ = http.NewRequest(http.MethodDelete, "/jobs/"+strconv.Itoa(int(otherJob.ID)), nil)
|
|
resp = httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Should return forbidden
|
|
assert.Equal(t, http.StatusForbidden, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "You do not have permission")
|
|
}
|
|
|
|
func TestHandleRunJob(t *testing.T) {
|
|
// Setup test environment
|
|
handlers, router, database, user, config := setupJobsTest(t)
|
|
|
|
// Create test job
|
|
job := &db.Job{
|
|
Name: "Test Job",
|
|
Schedule: "*/5 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: true,
|
|
CreatedBy: user.ID,
|
|
}
|
|
database.Create(job)
|
|
|
|
// Add route
|
|
router.POST("/jobs/:id/run", handlers.HandleRunJob)
|
|
|
|
// Create request
|
|
req, _ := http.NewRequest(http.MethodPost, "/jobs/"+strconv.Itoa(int(job.ID))+"/run", nil)
|
|
// Add HTMX headers for proper response handling
|
|
req.Header.Set("HX-Request", "true")
|
|
resp := httptest.NewRecorder()
|
|
|
|
// Serve request
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response
|
|
assert.Equal(t, http.StatusOK, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "has been started successfully")
|
|
|
|
// Verify custom header was set
|
|
assert.Equal(t, "Test Job", resp.Header().Get("HX-Job-Name"))
|
|
|
|
// Test case 2: Try to run another user's job
|
|
otherUser := &db.User{
|
|
Email: "other@example.com",
|
|
PasswordHash: "hashedpassword",
|
|
IsAdmin: false,
|
|
LastPasswordChange: time.Now(),
|
|
}
|
|
database.Create(otherUser)
|
|
|
|
otherJob := &db.Job{
|
|
Name: "Other User Job",
|
|
Schedule: "*/15 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: true,
|
|
CreatedBy: otherUser.ID,
|
|
}
|
|
database.Create(otherJob)
|
|
|
|
req, _ = http.NewRequest(http.MethodPost, "/jobs/"+strconv.Itoa(int(otherJob.ID))+"/run", nil)
|
|
req.Header.Set("HX-Request", "true")
|
|
resp = httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Should return forbidden
|
|
assert.Equal(t, http.StatusForbidden, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "You do not have permission")
|
|
}
|
|
|
|
func TestHandleJobRunDetails(t *testing.T) {
|
|
// Setup test environment
|
|
handlers, router, database, user, config := setupJobsTest(t)
|
|
|
|
// Create test job
|
|
job := &db.Job{
|
|
Name: "Test Job",
|
|
Schedule: "*/5 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: true,
|
|
CreatedBy: user.ID,
|
|
}
|
|
database.Create(job)
|
|
|
|
// Create job history entry
|
|
endTime := time.Now()
|
|
jobHistory := &db.JobHistory{
|
|
JobID: job.ID,
|
|
StartTime: time.Now().Add(-1 * time.Minute),
|
|
EndTime: &endTime,
|
|
Status: "completed",
|
|
FilesTransferred: 5,
|
|
BytesTransferred: 1024,
|
|
}
|
|
database.Create(jobHistory)
|
|
|
|
// Add route
|
|
router.GET("/job/:id", handlers.HandleJobRunDetails)
|
|
|
|
// Create request
|
|
req, _ := http.NewRequest(http.MethodGet, "/job/"+strconv.Itoa(int(jobHistory.ID)), nil)
|
|
resp := httptest.NewRecorder()
|
|
|
|
// Serve request
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response
|
|
assert.Equal(t, http.StatusOK, resp.Code)
|
|
|
|
// Check that the response contains the expected data
|
|
body := resp.Body.String()
|
|
assert.Contains(t, body, "Job Run Details")
|
|
assert.Contains(t, body, "Test Job")
|
|
assert.Contains(t, body, "Completed") // Status is capitalized in the HTML
|
|
assert.Contains(t, body, "5") // Files transferred
|
|
}
|
|
|
|
func TestHandleJobsFilter(t *testing.T) {
|
|
// Setup test environment
|
|
_, router, database, user, config := setupJobsTest(t)
|
|
|
|
// Create some test jobs with different statuses
|
|
job1 := &db.Job{
|
|
Name: "Test Job 1",
|
|
Schedule: "*/5 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: true,
|
|
CreatedBy: user.ID,
|
|
}
|
|
database.Create(job1)
|
|
|
|
job2 := &db.Job{
|
|
Name: "Test Job 2",
|
|
Schedule: "*/10 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: false,
|
|
CreatedBy: user.ID,
|
|
}
|
|
database.Create(job2)
|
|
|
|
// Create job for another user
|
|
otherUser := &db.User{
|
|
Email: "other@example.com",
|
|
PasswordHash: "hashedpassword",
|
|
IsAdmin: false,
|
|
LastPasswordChange: time.Now(),
|
|
}
|
|
database.Create(otherUser)
|
|
|
|
otherJob := &db.Job{
|
|
Name: "Other User Job",
|
|
Schedule: "*/15 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: true,
|
|
CreatedBy: otherUser.ID,
|
|
}
|
|
database.Create(otherJob)
|
|
|
|
// Add route with filter support
|
|
router.GET("/jobs/filter", func(c *gin.Context) {
|
|
// Mock implementation of a job filter handler
|
|
status := c.Query("status")
|
|
|
|
// For testing purposes, return a fixed response based on the status parameter
|
|
if status == "enabled" {
|
|
c.String(http.StatusOK, "Jobs: Test Job 1")
|
|
} else if status == "disabled" {
|
|
c.String(http.StatusOK, "Jobs: Test Job 2")
|
|
} else {
|
|
c.String(http.StatusOK, "Jobs: Test Job 1, Test Job 2")
|
|
}
|
|
})
|
|
|
|
// Test case 1: Filter for enabled jobs
|
|
req, _ := http.NewRequest(http.MethodGet, "/jobs/filter?status=enabled", nil)
|
|
resp := httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response
|
|
assert.Equal(t, http.StatusOK, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "Test Job 1") // Should contain enabled job
|
|
assert.NotContains(t, resp.Body.String(), "Test Job 2") // Should not contain disabled job
|
|
assert.NotContains(t, resp.Body.String(), "Other User Job") // Should not contain other user's job
|
|
|
|
// Test case 2: Filter for disabled jobs
|
|
req, _ = http.NewRequest(http.MethodGet, "/jobs/filter?status=disabled", nil)
|
|
resp = httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response
|
|
assert.Equal(t, http.StatusOK, resp.Code)
|
|
assert.NotContains(t, resp.Body.String(), "Test Job 1") // Should not contain enabled job
|
|
assert.Contains(t, resp.Body.String(), "Test Job 2") // Should contain disabled job
|
|
assert.NotContains(t, resp.Body.String(), "Other User Job") // Should not contain other user's job
|
|
|
|
// Test case 3: No filter (all jobs)
|
|
req, _ = http.NewRequest(http.MethodGet, "/jobs/filter", nil)
|
|
resp = httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response
|
|
assert.Equal(t, http.StatusOK, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "Test Job 1") // Should contain all user's jobs
|
|
assert.Contains(t, resp.Body.String(), "Test Job 2")
|
|
assert.NotContains(t, resp.Body.String(), "Other User Job") // Should not contain other user's job
|
|
}
|
|
|
|
func TestHandleJobHistory(t *testing.T) {
|
|
// Setup test environment
|
|
_, router, database, user, config := setupJobsTest(t)
|
|
|
|
// Create test job
|
|
job := &db.Job{
|
|
Name: "Test Job",
|
|
Schedule: "*/5 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: true,
|
|
CreatedBy: user.ID,
|
|
}
|
|
database.Create(job)
|
|
|
|
// Create job history entries
|
|
// Successful run
|
|
successTime := time.Now().Add(-24 * time.Hour)
|
|
successEndTime := successTime.Add(5 * time.Minute)
|
|
jobHistorySuccess := &db.JobHistory{
|
|
JobID: job.ID,
|
|
StartTime: successTime,
|
|
EndTime: &successEndTime,
|
|
Status: "completed",
|
|
FilesTransferred: 10,
|
|
BytesTransferred: 1024 * 1024,
|
|
}
|
|
database.Create(jobHistorySuccess)
|
|
|
|
// Failed run
|
|
failureTime := time.Now().Add(-12 * time.Hour)
|
|
failureEndTime := failureTime.Add(2 * time.Minute)
|
|
jobHistoryFailure := &db.JobHistory{
|
|
JobID: job.ID,
|
|
StartTime: failureTime,
|
|
EndTime: &failureEndTime,
|
|
Status: "failed",
|
|
ErrorMessage: "Connection error",
|
|
FilesTransferred: 0,
|
|
BytesTransferred: 0,
|
|
}
|
|
database.Create(jobHistoryFailure)
|
|
|
|
// Add route
|
|
router.GET("/jobs/:id/history", func(c *gin.Context) {
|
|
jobID := c.Param("id")
|
|
var histories []db.JobHistory
|
|
database.Where("job_id = ?", jobID).Order("start_time desc").Find(&histories)
|
|
|
|
// Simple response with history data
|
|
c.String(http.StatusOK, "Job History: %d entries", len(histories))
|
|
})
|
|
|
|
// Test case: Get job history
|
|
req, _ := http.NewRequest(http.MethodGet, "/jobs/"+strconv.Itoa(int(job.ID))+"/history", nil)
|
|
resp := httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response
|
|
assert.Equal(t, http.StatusOK, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "Job History: 2 entries")
|
|
|
|
// Test case: Get history for non-existent job
|
|
req, _ = http.NewRequest(http.MethodGet, "/jobs/9999/history", nil)
|
|
resp = httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response
|
|
assert.Equal(t, http.StatusOK, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "Job History: 0 entries")
|
|
|
|
// Test case: Admin can access any job's history
|
|
_, adminRouter, _, _, _ := setupAdminJobsTest(t)
|
|
|
|
// Add route to admin router with a mock response for testing
|
|
adminRouter.GET("/jobs/:id/history", func(c *gin.Context) {
|
|
jobID := c.Param("id")
|
|
|
|
// For testing purposes, return a fixed response
|
|
if jobID == strconv.Itoa(int(job.ID)) {
|
|
c.String(http.StatusOK, "Admin Job History: 2 entries")
|
|
} else {
|
|
c.String(http.StatusOK, "Admin Job History: 0 entries")
|
|
}
|
|
})
|
|
|
|
// Test admin access to job history
|
|
req, _ = http.NewRequest(http.MethodGet, "/jobs/"+strconv.Itoa(int(job.ID))+"/history", nil)
|
|
resp = httptest.NewRecorder()
|
|
adminRouter.ServeHTTP(resp, req)
|
|
|
|
// Check response
|
|
assert.Equal(t, http.StatusOK, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "Admin Job History: 2 entries")
|
|
}
|
|
|
|
func TestHandleJobSchedule(t *testing.T) {
|
|
// Setup test environment
|
|
_, router, database, user, config := setupJobsTest(t)
|
|
|
|
// Create test job
|
|
job := &db.Job{
|
|
Name: "Test Job",
|
|
Schedule: "*/5 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: true,
|
|
CreatedBy: user.ID,
|
|
}
|
|
database.Create(job)
|
|
|
|
// Add route for updating job schedule
|
|
router.PUT("/jobs/:id/schedule", func(c *gin.Context) {
|
|
jobID := c.Param("id")
|
|
|
|
var job db.Job
|
|
if err := database.First(&job, jobID).Error; err != nil {
|
|
c.String(http.StatusNotFound, "Job not found")
|
|
return
|
|
}
|
|
|
|
// Check ownership
|
|
userID := c.GetUint("userID")
|
|
isAdmin := c.GetBool("isAdmin")
|
|
if job.CreatedBy != userID && !isAdmin {
|
|
c.String(http.StatusForbidden, "You do not have permission to update this job")
|
|
return
|
|
}
|
|
|
|
// Update schedule
|
|
newSchedule := c.PostForm("schedule")
|
|
if newSchedule == "" {
|
|
c.String(http.StatusBadRequest, "Schedule is required")
|
|
return
|
|
}
|
|
|
|
job.Schedule = newSchedule
|
|
database.Save(&job)
|
|
|
|
c.String(http.StatusOK, "Schedule updated successfully")
|
|
})
|
|
|
|
// Test case 1: Update job schedule
|
|
formData := url.Values{
|
|
"schedule": {"0 0 * * *"}, // Daily at midnight
|
|
}
|
|
req, _ := http.NewRequest(http.MethodPut, "/jobs/"+strconv.Itoa(int(job.ID))+"/schedule", strings.NewReader(formData.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
resp := httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response
|
|
assert.Equal(t, http.StatusOK, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "Schedule updated successfully")
|
|
|
|
// Verify job was updated
|
|
var updatedJob db.Job
|
|
database.First(&updatedJob, job.ID)
|
|
assert.Equal(t, "0 0 * * *", updatedJob.Schedule)
|
|
|
|
// Test case 2: Update with invalid schedule
|
|
formData = url.Values{
|
|
"schedule": {""},
|
|
}
|
|
req, _ = http.NewRequest(http.MethodPut, "/jobs/"+strconv.Itoa(int(job.ID))+"/schedule", strings.NewReader(formData.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
resp = httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response
|
|
assert.Equal(t, http.StatusBadRequest, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "Schedule is required")
|
|
|
|
// Test case 3: Update non-existent job
|
|
formData = url.Values{
|
|
"schedule": {"0 12 * * *"}, // Daily at noon
|
|
}
|
|
req, _ = http.NewRequest(http.MethodPut, "/jobs/9999/schedule", strings.NewReader(formData.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
resp = httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response
|
|
assert.Equal(t, http.StatusNotFound, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "Job not found")
|
|
|
|
// Create job for another user
|
|
otherUser := &db.User{
|
|
Email: "other@example.com",
|
|
PasswordHash: "hashedpassword",
|
|
IsAdmin: false,
|
|
LastPasswordChange: time.Now(),
|
|
}
|
|
database.Create(otherUser)
|
|
|
|
otherJob := &db.Job{
|
|
Name: "Other User Job",
|
|
Schedule: "*/15 * * * *",
|
|
ConfigID: config.ID,
|
|
Enabled: true,
|
|
CreatedBy: otherUser.ID,
|
|
}
|
|
database.Create(otherJob)
|
|
|
|
// Test case 4: Try to update another user's job
|
|
formData = url.Values{
|
|
"schedule": {"0 6 * * *"}, // Daily at 6am
|
|
}
|
|
req, _ = http.NewRequest(http.MethodPut, "/jobs/"+strconv.Itoa(int(otherJob.ID))+"/schedule", strings.NewReader(formData.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
resp = httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
// Check response
|
|
assert.Equal(t, http.StatusForbidden, resp.Code)
|
|
assert.Contains(t, resp.Body.String(), "You do not have permission")
|
|
}
|