a31f79c22a
- automation/engine_test.go: 8 tests (cron parser, actions, start/stop) - handlers/crm_test.go: 6 tests (CRUD + not-found) - backend/main_test.go: config validation test - Fixed websocket unreachable code - Deleted events/kafka.go placeholder
195 lines
4.6 KiB
Go
195 lines
4.6 KiB
Go
package automation
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/DATA-DOG/go-sqlmock"
|
|
"github.com/rs/zerolog"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestCalculateNextRun(t *testing.T) {
|
|
e := &Engine{}
|
|
|
|
tests := []struct {
|
|
name string
|
|
cron string
|
|
tz string
|
|
wantHour int // approximate check
|
|
}{
|
|
{"daily midnight", "0 0 * * *", "UTC", 0},
|
|
{"every hour", "0 * * * *", "UTC", -1}, // any hour
|
|
{"every 5 min", "*/5 * * * *", "UTC", -1},
|
|
{"invalid fallback", "0 0 * * *", "Bad/Timezone", -1},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := e.calculateNextRun(tt.cron, tt.tz)
|
|
require.NoError(t, err)
|
|
assert.True(t, got.After(time.Now()), "next run should be in the future")
|
|
if tt.wantHour >= 0 {
|
|
assert.Equal(t, tt.wantHour, got.Hour())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCalculateNextRun_Invalid(t *testing.T) {
|
|
e := &Engine{}
|
|
_, err := e.calculateNextRun("not-a-cron", "UTC")
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestExecuteAction_SendEmail(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
require.NoError(t, err)
|
|
defer db.Close()
|
|
|
|
e := NewEngine(db, zerolog.New(nil))
|
|
ctx := context.Background()
|
|
|
|
// Valid email action
|
|
err = e.executeAction(ctx, "tenant-1", map[string]interface{}{
|
|
"type": "send_email",
|
|
"to": "test@example.com",
|
|
"subject": "Hello",
|
|
"body": "World",
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
// Missing required fields
|
|
err = e.executeAction(ctx, "tenant-1", map[string]interface{}{
|
|
"type": "send_email",
|
|
"to": "test@example.com",
|
|
})
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "requires")
|
|
|
|
// Unknown action
|
|
err = e.executeAction(ctx, "tenant-1", map[string]interface{}{
|
|
"type": "unknown_action",
|
|
})
|
|
assert.Error(t, err)
|
|
|
|
assert.NoError(t, mock.ExpectationsWereMet())
|
|
}
|
|
|
|
func TestExecuteAction_CreateTask(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
require.NoError(t, err)
|
|
defer db.Close()
|
|
|
|
e := NewEngine(db, zerolog.New(nil))
|
|
ctx := context.Background()
|
|
|
|
mock.ExpectExec("INSERT INTO boc_tickets").
|
|
WithArgs("tenant-1", "Fix bug", "user-1").
|
|
WillReturnResult(sqlmock.NewResult(1, 1))
|
|
|
|
err = e.executeAction(ctx, "tenant-1", map[string]interface{}{
|
|
"type": "create_task",
|
|
"title": "Fix bug",
|
|
"assignee": "user-1",
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, mock.ExpectationsWereMet())
|
|
}
|
|
|
|
func TestExecuteAction_UpdateRecord(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
require.NoError(t, err)
|
|
defer db.Close()
|
|
|
|
e := NewEngine(db, zerolog.New(nil))
|
|
ctx := context.Background()
|
|
|
|
// Valid update
|
|
mock.ExpectExec("UPDATE boc_customers SET status = \\$(.+) WHERE id = \\$(.+) AND tenant_id = \\$(.+)").
|
|
WithArgs("active", "cust-1", "tenant-1").
|
|
WillReturnResult(sqlmock.NewResult(1, 1))
|
|
|
|
err = e.executeAction(ctx, "tenant-1", map[string]interface{}{
|
|
"type": "update_record",
|
|
"table": "boc_customers",
|
|
"record_id": "cust-1",
|
|
"field": "status",
|
|
"value": "active",
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
// Invalid table (SQL injection attempt)
|
|
err = e.executeAction(ctx, "tenant-1", map[string]interface{}{
|
|
"type": "update_record",
|
|
"table": "users; DROP TABLE boc_customers;--",
|
|
"record_id": "1",
|
|
"field": "name",
|
|
"value": "hacked",
|
|
})
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "not allowed")
|
|
|
|
assert.NoError(t, mock.ExpectationsWereMet())
|
|
}
|
|
|
|
func TestExecuteAction_Webhook(t *testing.T) {
|
|
db, mock, err := sqlmock.New()
|
|
require.NoError(t, err)
|
|
defer db.Close()
|
|
|
|
e := NewEngine(db, zerolog.New(nil))
|
|
ctx := context.Background()
|
|
|
|
// Valid webhook (currently just logs)
|
|
err = e.executeAction(ctx, "tenant-1", map[string]interface{}{
|
|
"type": "webhook",
|
|
"url": "https://example.com/webhook",
|
|
})
|
|
assert.NoError(t, err)
|
|
|
|
// Missing URL
|
|
err = e.executeAction(ctx, "tenant-1", map[string]interface{}{
|
|
"type": "webhook",
|
|
})
|
|
assert.Error(t, err)
|
|
|
|
assert.NoError(t, mock.ExpectationsWereMet())
|
|
}
|
|
|
|
func TestEngine_StartStop(t *testing.T) {
|
|
db, _, err := sqlmock.New()
|
|
require.NoError(t, err)
|
|
defer db.Close()
|
|
|
|
e := NewEngine(db, zerolog.New(nil))
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
e.Start(ctx)
|
|
time.Sleep(50 * time.Millisecond) // Let it start
|
|
|
|
// Stop should not panic
|
|
cancel()
|
|
e.Stop()
|
|
}
|
|
|
|
func TestRunReportJob(t *testing.T) {
|
|
db, _, err := sqlmock.New()
|
|
require.NoError(t, err)
|
|
defer db.Close()
|
|
|
|
e := NewEngine(db, zerolog.New(nil))
|
|
ctx := context.Background()
|
|
|
|
job := ScheduledJob{
|
|
JobConfig: map[string]interface{}{"report_type": "monthly_sales"},
|
|
}
|
|
|
|
output, err := e.runReportJob(ctx, job)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "monthly_sales", output["report_type"])
|
|
assert.Equal(t, "generated", output["status"])
|
|
}
|