LINUS ROUND 2: Tests for automation + CRM handlers, fix vet errors
- 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
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
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"])
|
||||
}
|
||||
Reference in New Issue
Block a user