mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-10-11 08:47:28 +02:00
feat: Implement webhook notifications and admin tools for job management
- Add webhook notification settings to job configuration, allowing users to enable notifications for job success and failure. - Implement webhook payload structure and authentication using HMAC-SHA256 for secure communication. - Enhance the admin tools with a log viewer and system management features, including database backup and log file access. - Update README documentation to include details on webhook integration and admin tools. - Introduce comprehensive tests for webhook functionality, ensuring correct payload delivery and header validation. - Add database migrations to support new webhook fields in job configurations.
This commit is contained in:
1 parent
558e81c7e8
commit
d5b0a686e0
14 files changed
+2058
-19
No files matched your search
@@ -0,0 +1,243 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/starfleetcptn/gomft/internal/db"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestWebhookConfiguration tests the webhook configuration during job creation and editing
|
||||
func TestWebhookConfiguration(t *testing.T) {
|
||||
// Set up test environment
|
||||
handlers, router, database, user, config := setupJobsTest(t)
|
||||
|
||||
// Add job create route
|
||||
router.POST("/jobs/create", handlers.HandleCreateJob)
|
||||
|
||||
// Create job form data with webhook enabled
|
||||
formData := url.Values{
|
||||
"name": {"Webhook Test Job"},
|
||||
"config_ids[]": {strconv.Itoa(int(config.ID))},
|
||||
"schedule": {"*/15 * * * *"},
|
||||
"enabled": {"true"},
|
||||
"webhook_enabled": {"true"},
|
||||
"webhook_url": {"https://example.com/webhook"},
|
||||
"webhook_secret": {"test-secret"},
|
||||
"webhook_headers": {`{"X-Test-Header": "test-value"}`},
|
||||
"notify_on_success": {"true"},
|
||||
"notify_on_failure": {"true"},
|
||||
}
|
||||
|
||||
// Submit form
|
||||
req, _ := http.NewRequest("POST", "/jobs/create", strings.NewReader(formData.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp := httptest.NewRecorder()
|
||||
router.ServeHTTP(resp, req)
|
||||
|
||||
// Should redirect on success
|
||||
assert.Equal(t, http.StatusFound, resp.Code)
|
||||
|
||||
// Check if job was created with webhook settings
|
||||
var jobs []db.Job
|
||||
err := database.DB.Where("created_by = ?", user.ID).Find(&jobs).Error
|
||||
require.NoError(t, err)
|
||||
require.GreaterOrEqual(t, len(jobs), 1)
|
||||
|
||||
// Get the most recently created job
|
||||
var job db.Job
|
||||
err = database.DB.Where("created_by = ?", user.ID).Order("created_at DESC").First(&job).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify webhook settings were saved correctly
|
||||
assert.True(t, job.WebhookEnabled)
|
||||
assert.Equal(t, "https://example.com/webhook", job.WebhookURL)
|
||||
assert.Equal(t, "test-secret", job.WebhookSecret)
|
||||
assert.Equal(t, `{"X-Test-Header": "test-value"}`, job.WebhookHeaders)
|
||||
assert.True(t, job.NotifyOnSuccess)
|
||||
assert.True(t, job.NotifyOnFailure)
|
||||
}
|
||||
|
||||
// TestWebhookEditConfiguration tests editing webhook configuration
|
||||
func TestWebhookEditConfiguration(t *testing.T) {
|
||||
// Set up test environment
|
||||
handlers, router, database, user, config := setupJobsTest(t)
|
||||
|
||||
// Create a job first
|
||||
job := &db.Job{
|
||||
Name: "Initial Job",
|
||||
ConfigID: config.ID,
|
||||
Schedule: "*/30 * * * *",
|
||||
Enabled: true,
|
||||
WebhookEnabled: false, // Initially disabled
|
||||
CreatedBy: user.ID,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := database.DB.Create(job).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add job update route
|
||||
router.PUT("/jobs/:id", handlers.HandleUpdateJob)
|
||||
|
||||
// Create edit form data to enable webhook
|
||||
formData := url.Values{
|
||||
"name": {"Updated Job"},
|
||||
"config_ids[]": {strconv.Itoa(int(config.ID))},
|
||||
"schedule": {"*/30 * * * *"},
|
||||
"enabled": {"true"},
|
||||
"webhook_enabled": {"true"}, // Enabling webhook
|
||||
"webhook_url": {"https://example.com/webhook"}, // Adding URL
|
||||
"webhook_secret": {"new-secret"}, // Adding secret
|
||||
"webhook_headers": {`{"X-Api-Key": "12345"}`}, // Adding headers
|
||||
"notify_on_success": {"true"}, // Configure notifications
|
||||
"notify_on_failure": {"false"}, // Only notify on success
|
||||
}
|
||||
|
||||
// Submit edit form
|
||||
req, _ := http.NewRequest("PUT", "/jobs/"+strconv.Itoa(int(job.ID)), strings.NewReader(formData.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp := httptest.NewRecorder()
|
||||
router.ServeHTTP(resp, req)
|
||||
|
||||
// Should redirect on success
|
||||
assert.Equal(t, http.StatusFound, resp.Code)
|
||||
|
||||
// Get the updated job
|
||||
var updatedJob db.Job
|
||||
err = database.DB.First(&updatedJob, job.ID).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify webhook settings were updated correctly
|
||||
assert.True(t, updatedJob.WebhookEnabled)
|
||||
assert.Equal(t, "https://example.com/webhook", updatedJob.WebhookURL)
|
||||
assert.Equal(t, "new-secret", updatedJob.WebhookSecret)
|
||||
assert.Equal(t, `{"X-Api-Key": "12345"}`, updatedJob.WebhookHeaders)
|
||||
assert.True(t, updatedJob.NotifyOnSuccess)
|
||||
assert.False(t, updatedJob.NotifyOnFailure)
|
||||
}
|
||||
|
||||
// TestDisablingWebhook tests disabling a previously enabled webhook
|
||||
func TestDisablingWebhook(t *testing.T) {
|
||||
// Set up test environment
|
||||
handlers, router, database, user, config := setupJobsTest(t)
|
||||
|
||||
// Create a job with webhook enabled
|
||||
job := &db.Job{
|
||||
Name: "Webhook Enabled Job",
|
||||
ConfigID: config.ID,
|
||||
Schedule: "*/30 * * * *",
|
||||
Enabled: true,
|
||||
WebhookEnabled: true,
|
||||
WebhookURL: "https://example.com/webhook",
|
||||
WebhookSecret: "secret",
|
||||
WebhookHeaders: `{"X-Test": "test"}`,
|
||||
NotifyOnSuccess: true,
|
||||
NotifyOnFailure: true,
|
||||
CreatedBy: user.ID,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := database.DB.Create(job).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add job update route
|
||||
router.PUT("/jobs/:id", handlers.HandleUpdateJob)
|
||||
|
||||
// Create edit form data to disable webhook
|
||||
formData := url.Values{
|
||||
"name": {"Webhook Disabled Job"},
|
||||
"config_ids[]": {strconv.Itoa(int(config.ID))},
|
||||
"schedule": {"*/30 * * * *"},
|
||||
"enabled": {"true"},
|
||||
"webhook_enabled": {"false"}, // Explicitly set to false
|
||||
"webhook_url": {"https://example.com/webhook"}, // URL remains the same
|
||||
"webhook_secret": {"secret"}, // Secret remains the same
|
||||
"webhook_headers": {`{"X-Test": "test"}`}, // Headers remain the same
|
||||
}
|
||||
|
||||
// Submit edit form
|
||||
req, _ := http.NewRequest("PUT", "/jobs/"+strconv.Itoa(int(job.ID)), strings.NewReader(formData.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp := httptest.NewRecorder()
|
||||
router.ServeHTTP(resp, req)
|
||||
|
||||
// Should redirect on success
|
||||
assert.Equal(t, http.StatusFound, resp.Code)
|
||||
|
||||
// Get the updated job
|
||||
var updatedJob db.Job
|
||||
err = database.DB.First(&updatedJob, job.ID).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify webhook was disabled
|
||||
assert.False(t, updatedJob.WebhookEnabled)
|
||||
|
||||
// Other fields should remain unchanged
|
||||
assert.Equal(t, "https://example.com/webhook", updatedJob.WebhookURL)
|
||||
assert.Equal(t, "secret", updatedJob.WebhookSecret)
|
||||
assert.Equal(t, `{"X-Test": "test"}`, updatedJob.WebhookHeaders)
|
||||
}
|
||||
|
||||
// TestWebhookValidation tests validation of webhook URL
|
||||
func TestWebhookValidation(t *testing.T) {
|
||||
// Set up test environment
|
||||
handlers, router, _, _, config := setupJobsTest(t)
|
||||
|
||||
// Add job create route
|
||||
router.POST("/jobs/create", handlers.HandleCreateJob)
|
||||
|
||||
// Create job form data with invalid webhook URL
|
||||
formData := url.Values{
|
||||
"name": {"Invalid Webhook Job"},
|
||||
"config_ids[]": {strconv.Itoa(int(config.ID))},
|
||||
"schedule": {"*/15 * * * *"},
|
||||
"enabled": {"true"},
|
||||
"webhook_enabled": {"true"},
|
||||
"webhook_url": {"invalid-url"}, // Invalid URL
|
||||
"webhook_secret": {"test-secret"},
|
||||
"notify_on_success": {"true"},
|
||||
"notify_on_failure": {"true"},
|
||||
}
|
||||
|
||||
// Submit form
|
||||
req, _ := http.NewRequest("POST", "/jobs/create", strings.NewReader(formData.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp := httptest.NewRecorder()
|
||||
router.ServeHTTP(resp, req)
|
||||
|
||||
// Should not create job with invalid webhook URL
|
||||
assert.NotEqual(t, http.StatusFound, resp.Code)
|
||||
assert.Contains(t, resp.Body.String(), "valid URL")
|
||||
|
||||
// Test invalid headers JSON
|
||||
formData = url.Values{
|
||||
"name": {"Invalid Headers Job"},
|
||||
"config_ids[]": {strconv.Itoa(int(config.ID))},
|
||||
"schedule": {"*/15 * * * *"},
|
||||
"enabled": {"true"},
|
||||
"webhook_enabled": {"true"},
|
||||
"webhook_url": {"https://example.com/webhook"},
|
||||
"webhook_secret": {"test-secret"},
|
||||
"webhook_headers": {`{"invalid json`}, // Invalid JSON
|
||||
"notify_on_success": {"true"},
|
||||
"notify_on_failure": {"true"},
|
||||
}
|
||||
|
||||
// Submit form
|
||||
req, _ = http.NewRequest("POST", "/jobs/create", strings.NewReader(formData.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp = httptest.NewRecorder()
|
||||
router.ServeHTTP(resp, req)
|
||||
|
||||
// Should not create job with invalid headers JSON
|
||||
assert.NotEqual(t, http.StatusFound, resp.Code)
|
||||
assert.Contains(t, resp.Body.String(), "valid JSON")
|
||||
}
|
||||
Reference in new issue
Block a user