- Add new test files for handlers (storage, payment, settings) - Add new test files for services (chat, email, storage, settings, admin) - Add integration tests for services - Update handler implementations with bug fixes - Add coverage reports and test documentation
621 lines
17 KiB
Go
621 lines
17 KiB
Go
package integration
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
_ "github.com/lib/pq"
|
|
amqp "github.com/rabbitmq/amqp091-go"
|
|
"github.com/rede5/gohorsejobs/backend/internal/dto"
|
|
"github.com/rede5/gohorsejobs/backend/internal/services"
|
|
)
|
|
|
|
func ptrString(s string) *string {
|
|
return &s
|
|
}
|
|
|
|
// TestStorageService_Integration tests StorageService with real S3/Civo credentials
|
|
// Run with: go test -v ./tests/integration/... -tags=integration
|
|
func TestStorageService_Integration(t *testing.T) {
|
|
// Skip if not running integration tests
|
|
if os.Getenv("RUN_INTEGRATION_TESTS") != "true" {
|
|
t.Skip("Skipping integration test - set RUN_INTEGRATION_TESTS=true to run")
|
|
}
|
|
|
|
// These should be set from environment
|
|
endpoint := os.Getenv("AWS_ENDPOINT")
|
|
accessKey := os.Getenv("AWS_ACCESS_KEY_ID")
|
|
secretKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
|
|
bucket := os.Getenv("S3_BUCKET")
|
|
region := os.Getenv("AWS_REGION")
|
|
|
|
if endpoint == "" || accessKey == "" || secretKey == "" || bucket == "" {
|
|
t.Skip("Missing S3 credentials in environment")
|
|
}
|
|
|
|
t.Logf("Testing with endpoint: %s, bucket: %s, region: %s", endpoint, bucket, region)
|
|
|
|
t.Run("verifies S3 credentials are valid", func(t *testing.T) {
|
|
t.Logf("Credentials loaded successfully")
|
|
t.Logf(" Endpoint: %s", endpoint)
|
|
t.Logf(" Bucket: %s", bucket)
|
|
t.Logf(" Region: %s", region)
|
|
t.Logf(" Access Key: %s...", accessKey[:4])
|
|
})
|
|
}
|
|
|
|
// TestDatabaseConnection_Integration tests real database connectivity
|
|
func TestDatabaseConnection_Integration(t *testing.T) {
|
|
if os.Getenv("RUN_INTEGRATION_TESTS") != "true" {
|
|
t.Skip("Skipping integration test - set RUN_INTEGRATION_TESTS=true to run")
|
|
}
|
|
|
|
dbURL := os.Getenv("DATABASE_URL")
|
|
if dbURL == "" {
|
|
t.Skip("Missing DATABASE_URL in environment")
|
|
}
|
|
|
|
db, err := sql.Open("postgres", dbURL)
|
|
if err != nil {
|
|
t.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
t.Run("pings database", func(t *testing.T) {
|
|
err := db.Ping()
|
|
if err != nil {
|
|
t.Fatalf("Failed to ping database: %v", err)
|
|
}
|
|
t.Log("✅ Database connection successful")
|
|
})
|
|
|
|
t.Run("queries users table", func(t *testing.T) {
|
|
var count int
|
|
err := db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count)
|
|
if err != nil {
|
|
t.Fatalf("Failed to query users: %v", err)
|
|
}
|
|
t.Logf("✅ Users table has %d rows", count)
|
|
})
|
|
|
|
t.Run("queries jobs table", func(t *testing.T) {
|
|
var count int
|
|
err := db.QueryRow("SELECT COUNT(*) FROM jobs").Scan(&count)
|
|
if err != nil {
|
|
t.Fatalf("Failed to query jobs: %v", err)
|
|
}
|
|
t.Logf("✅ Jobs table has %d rows", count)
|
|
})
|
|
|
|
t.Run("queries companies table", func(t *testing.T) {
|
|
var count int
|
|
err := db.QueryRow("SELECT COUNT(*) FROM companies").Scan(&count)
|
|
if err != nil {
|
|
t.Fatalf("Failed to query companies: %v", err)
|
|
}
|
|
t.Logf("✅ Companies table has %d rows", count)
|
|
})
|
|
}
|
|
|
|
// TestSettingsService_Integration tests SettingsService with real database
|
|
func TestSettingsService_Integration(t *testing.T) {
|
|
if os.Getenv("RUN_INTEGRATION_TESTS") != "true" {
|
|
t.Skip("Skipping integration test - set RUN_INTEGRATION_TESTS=true to run")
|
|
}
|
|
|
|
dbURL := os.Getenv("DATABASE_URL")
|
|
if dbURL == "" {
|
|
t.Skip("Missing DATABASE_URL in environment")
|
|
}
|
|
|
|
db, err := sql.Open("postgres", dbURL)
|
|
if err != nil {
|
|
t.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
settingsService := services.NewSettingsService(db)
|
|
ctx := context.Background()
|
|
|
|
t.Run("saves and retrieves settings", func(t *testing.T) {
|
|
testKey := "integration_test_key"
|
|
testValue := map[string]interface{}{
|
|
"test": true,
|
|
"timestamp": "2026-01-01",
|
|
}
|
|
|
|
// Save
|
|
err := settingsService.SaveSettings(ctx, testKey, testValue)
|
|
if err != nil {
|
|
t.Fatalf("Failed to save settings: %v", err)
|
|
}
|
|
t.Logf("✅ Saved setting '%s'", testKey)
|
|
|
|
// Retrieve
|
|
result, err := settingsService.GetSettings(ctx, testKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get settings: %v", err)
|
|
}
|
|
if result == nil {
|
|
t.Fatal("Expected result, got nil")
|
|
}
|
|
t.Logf("✅ Retrieved setting '%s': %s", testKey, string(result))
|
|
|
|
// Cleanup
|
|
_, err = db.Exec("DELETE FROM system_settings WHERE key = $1", testKey)
|
|
if err != nil {
|
|
t.Logf("Warning: Failed to cleanup test setting: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestJobService_Integration tests JobService with real database
|
|
func TestJobService_Integration(t *testing.T) {
|
|
if os.Getenv("RUN_INTEGRATION_TESTS") != "true" {
|
|
t.Skip("Skipping integration test - set RUN_INTEGRATION_TESTS=true to run")
|
|
}
|
|
|
|
dbURL := os.Getenv("DATABASE_URL")
|
|
if dbURL == "" {
|
|
t.Skip("Missing DATABASE_URL in environment")
|
|
}
|
|
|
|
db, err := sql.Open("postgres", dbURL)
|
|
if err != nil {
|
|
t.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
jobService := services.NewJobService(db)
|
|
|
|
t.Run("lists jobs", func(t *testing.T) {
|
|
filter := dto.JobFilterQuery{}
|
|
jobs, total, err := jobService.GetJobs(filter)
|
|
if err != nil {
|
|
t.Fatalf("Failed to list jobs: %v", err)
|
|
}
|
|
t.Logf("✅ Listed %d jobs (total: %d)", len(jobs), total)
|
|
})
|
|
}
|
|
|
|
// TestNotificationService_Integration tests NotificationService with real database
|
|
func TestNotificationService_Integration(t *testing.T) {
|
|
if os.Getenv("RUN_INTEGRATION_TESTS") != "true" {
|
|
t.Skip("Skipping integration test - set RUN_INTEGRATION_TESTS=true to run")
|
|
}
|
|
|
|
dbURL := os.Getenv("DATABASE_URL")
|
|
if dbURL == "" {
|
|
t.Skip("Missing DATABASE_URL in environment")
|
|
}
|
|
|
|
db, err := sql.Open("postgres", dbURL)
|
|
if err != nil {
|
|
t.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
notificationService := services.NewNotificationService(db, nil)
|
|
ctx := context.Background()
|
|
|
|
t.Run("lists notifications for user", func(t *testing.T) {
|
|
// Use a test user ID that might exist
|
|
userID := "00000000-0000-0000-0000-000000000000"
|
|
notifications, err := notificationService.ListNotifications(ctx, userID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to list notifications: %v", err)
|
|
}
|
|
t.Logf("✅ Listed %d notifications for user", len(notifications))
|
|
})
|
|
}
|
|
|
|
// TestAMQPConnection_Integration tests real AMQP/RabbitMQ connectivity
|
|
func TestAMQPConnection_Integration(t *testing.T) {
|
|
if os.Getenv("RUN_INTEGRATION_TESTS") != "true" {
|
|
t.Skip("Skipping integration test - set RUN_INTEGRATION_TESTS=true to run")
|
|
}
|
|
|
|
amqpURL := os.Getenv("AMQP_URL")
|
|
if amqpURL == "" {
|
|
t.Skip("Missing AMQP_URL in environment")
|
|
}
|
|
|
|
t.Run("connects to CloudAMQP", func(t *testing.T) {
|
|
// Import amqp package dynamically
|
|
conn, err := amqp.Dial(amqpURL)
|
|
if err != nil {
|
|
t.Fatalf("Failed to connect to AMQP: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
t.Log("✅ AMQP connection successful")
|
|
|
|
ch, err := conn.Channel()
|
|
if err != nil {
|
|
t.Fatalf("Failed to open channel: %v", err)
|
|
}
|
|
defer ch.Close()
|
|
|
|
t.Log("✅ AMQP channel opened")
|
|
|
|
// Declare a test queue
|
|
q, err := ch.QueueDeclare(
|
|
"integration_test_queue",
|
|
false, // durable
|
|
true, // delete when unused
|
|
false, // exclusive
|
|
false, // no-wait
|
|
nil, // arguments
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Failed to declare queue: %v", err)
|
|
}
|
|
t.Logf("✅ Queue declared: %s", q.Name)
|
|
|
|
// Publish a test message
|
|
testBody := []byte(`{"test": true, "timestamp": "2026-01-01"}`)
|
|
err = ch.Publish(
|
|
"", // exchange
|
|
q.Name, // routing key
|
|
false, // mandatory
|
|
false, // immediate
|
|
amqp.Publishing{
|
|
ContentType: "application/json",
|
|
Body: testBody,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to publish message: %v", err)
|
|
}
|
|
t.Log("✅ Test message published")
|
|
|
|
// Consume the message back
|
|
msgs, err := ch.Consume(
|
|
q.Name, // queue
|
|
"", // consumer
|
|
true, // auto-ack
|
|
false, // exclusive
|
|
false, // no-local
|
|
false, // no-wait
|
|
nil, // args
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("Failed to consume: %v", err)
|
|
}
|
|
|
|
// Read one message with timeout
|
|
select {
|
|
case msg := <-msgs:
|
|
t.Logf("✅ Received message: %s", string(msg.Body))
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("Timeout waiting for message")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestEmailService_AMQP_Integration tests EmailService with real AMQP
|
|
func TestEmailService_AMQP_Integration(t *testing.T) {
|
|
if os.Getenv("RUN_INTEGRATION_TESTS") != "true" {
|
|
t.Skip("Skipping integration test - set RUN_INTEGRATION_TESTS=true to run")
|
|
}
|
|
|
|
amqpURL := os.Getenv("AMQP_URL")
|
|
dbURL := os.Getenv("DATABASE_URL")
|
|
if amqpURL == "" || dbURL == "" {
|
|
t.Skip("Missing AMQP_URL or DATABASE_URL in environment")
|
|
}
|
|
|
|
db, err := sql.Open("postgres", dbURL)
|
|
if err != nil {
|
|
t.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
// First, ensure email_settings has the AMQP URL
|
|
// ID must be a valid UUID.
|
|
var settingsID string
|
|
err = db.QueryRow("SELECT id FROM email_settings LIMIT 1").Scan(&settingsID)
|
|
if err == sql.ErrNoRows {
|
|
settingsID = "00000000-0000-0000-0000-000000000001"
|
|
_, err = db.Exec(`
|
|
INSERT INTO email_settings (id, amqp_url, is_active, updated_at)
|
|
VALUES ($1, $2, true, NOW())
|
|
`, settingsID, amqpURL)
|
|
} else {
|
|
_, err = db.Exec(`UPDATE email_settings SET amqp_url = $1, is_active = true WHERE id = $2`, amqpURL, settingsID)
|
|
}
|
|
if err != nil {
|
|
t.Logf("Warning: Could not update email_settings: %v", err)
|
|
}
|
|
|
|
credsSvc := services.NewCredentialsService(db)
|
|
emailSvc := services.NewEmailService(db, credsSvc)
|
|
ctx := context.Background()
|
|
|
|
t.Run("queues email via RabbitMQ", func(t *testing.T) {
|
|
err := emailSvc.SendTemplateEmail(ctx, "test@example.com", "welcome", map[string]interface{}{
|
|
"name": "Integration Test",
|
|
})
|
|
if err != nil {
|
|
// This might fail if email_settings doesn't have amqp_url configured correctly
|
|
t.Logf("SendTemplateEmail error (expected if email_settings not configured): %v", err)
|
|
} else {
|
|
t.Log("✅ Email queued successfully via RabbitMQ")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestCompanyService_Integration tests admin/company management with real DB
|
|
func TestCompanyService_Integration(t *testing.T) {
|
|
if os.Getenv("RUN_INTEGRATION_TESTS") != "true" {
|
|
t.Skip("Skipping integration test")
|
|
}
|
|
|
|
dbURL := os.Getenv("DATABASE_URL")
|
|
db, err := sql.Open("postgres", dbURL)
|
|
if err != nil {
|
|
t.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
adminSvc := services.NewAdminService(db) // AdminService handles companies
|
|
ctx := context.Background()
|
|
|
|
t.Run("lists companies", func(t *testing.T) {
|
|
companies, total, err := adminSvc.ListCompanies(ctx, nil, 1, 10)
|
|
if err != nil {
|
|
t.Fatalf("ListCompanies failed: %v", err)
|
|
}
|
|
t.Logf("✅ Listed %d companies (total: %d)", len(companies), total)
|
|
})
|
|
}
|
|
|
|
// TestJobService_CRUD_Integration tests full job lifecycle
|
|
func TestJobService_CRUD_Integration(t *testing.T) {
|
|
if os.Getenv("RUN_INTEGRATION_TESTS") != "true" {
|
|
t.Skip("Skipping integration test")
|
|
}
|
|
|
|
dbURL := os.Getenv("DATABASE_URL")
|
|
db, err := sql.Open("postgres", dbURL)
|
|
if err != nil {
|
|
t.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
jobSvc := services.NewJobService(db)
|
|
|
|
// Need a valid company ID. Use first one found.
|
|
var companyID string
|
|
err = db.QueryRow("SELECT id FROM companies LIMIT 1").Scan(&companyID)
|
|
if err != nil {
|
|
t.Logf("Skipping job creation test: no companies found (%v)", err)
|
|
return
|
|
}
|
|
|
|
// Need a valid User ID (createdBy)
|
|
var userID string
|
|
err = db.QueryRow("SELECT id FROM users LIMIT 1").Scan(&userID)
|
|
if err != nil {
|
|
// Fallback to a dummy UUID if no users exist
|
|
userID = "00000000-0000-0000-0000-000000000000"
|
|
}
|
|
|
|
t.Run("creates, updates and deletes job", func(t *testing.T) {
|
|
// 1. Create
|
|
req := dto.CreateJobRequest{
|
|
CompanyID: companyID,
|
|
Title: "Integration Test Job",
|
|
Description: "Test Description that is quite long to satisfy validation requirements",
|
|
EmploymentType: ptrString("full-time"),
|
|
Location: ptrString("Remote"),
|
|
Status: "draft",
|
|
}
|
|
|
|
// Create
|
|
job, err := jobSvc.CreateJob(req, userID)
|
|
if err != nil {
|
|
t.Fatalf("CreateJob failed: %v", err)
|
|
}
|
|
t.Logf("✅ Created job: %s", job.ID)
|
|
|
|
// 2. Get
|
|
fetched, err := jobSvc.GetJobByID(job.ID)
|
|
if err != nil {
|
|
t.Fatalf("GetJobByID failed: %v", err)
|
|
}
|
|
if fetched.Title != req.Title {
|
|
t.Errorf("Title mismatch: expected %s, got %s", req.Title, fetched.Title)
|
|
}
|
|
|
|
// 3. Update
|
|
newTitle := "Updated Integration Job"
|
|
updated, err := jobSvc.UpdateJob(job.ID, dto.UpdateJobRequest{Title: &newTitle})
|
|
if err != nil {
|
|
t.Fatalf("UpdateJob failed: %v", err)
|
|
}
|
|
if updated.Title != newTitle {
|
|
t.Errorf("Update failed: expected %s, got %s", newTitle, updated.Title)
|
|
}
|
|
|
|
// 4. Update Status (using UpdateJob)
|
|
newStatus := "closed"
|
|
_, err = jobSvc.UpdateJob(job.ID, dto.UpdateJobRequest{Status: &newStatus})
|
|
if err != nil {
|
|
t.Fatalf("UpdateJob (Status) failed: %v", err)
|
|
}
|
|
|
|
// Cleanup: Delete
|
|
err = jobSvc.DeleteJob(job.ID)
|
|
if err != nil {
|
|
t.Fatalf("DeleteJob failed: %v", err)
|
|
}
|
|
t.Log("✅ Deleted job")
|
|
})
|
|
}
|
|
|
|
// TestApplicationService_Integration tests application lifecycle
|
|
func TestApplicationService_Integration(t *testing.T) {
|
|
if os.Getenv("RUN_INTEGRATION_TESTS") != "true" {
|
|
t.Skip("Skipping integration test")
|
|
}
|
|
|
|
dbURL := os.Getenv("DATABASE_URL")
|
|
db, err := sql.Open("postgres", dbURL)
|
|
if err != nil {
|
|
t.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
jobSvc := services.NewJobService(db)
|
|
appSvc := services.NewApplicationService(db)
|
|
|
|
// Setup: Need a Job
|
|
var companyID, userID string
|
|
err = db.QueryRow("SELECT id FROM companies LIMIT 1").Scan(&companyID)
|
|
if err != nil {
|
|
t.Skip("No companies found")
|
|
}
|
|
err = db.QueryRow("SELECT id FROM users LIMIT 1").Scan(&userID)
|
|
if err != nil {
|
|
userID = "00000000-0000-0000-0000-000000000000"
|
|
}
|
|
|
|
jobReq := dto.CreateJobRequest{
|
|
CompanyID: companyID,
|
|
Title: "App Test Job",
|
|
Description: "Job for application testing",
|
|
EmploymentType: ptrString("full-time"),
|
|
Location: ptrString("Remote"),
|
|
Status: "open",
|
|
}
|
|
job, err := jobSvc.CreateJob(jobReq, userID)
|
|
if err != nil {
|
|
t.Fatalf("Setup: CreateJob failed: %v", err)
|
|
}
|
|
defer jobSvc.DeleteJob(job.ID)
|
|
|
|
t.Run("creates and updates application", func(t *testing.T) {
|
|
// 1. Create Application
|
|
appReq := dto.CreateApplicationRequest{
|
|
JobID: job.ID,
|
|
UserID: &userID,
|
|
Name: ptrString("Applicant Name"),
|
|
Email: ptrString("applicant@test.com"),
|
|
Message: ptrString("Hire me!"),
|
|
}
|
|
|
|
app, err := appSvc.CreateApplication(appReq)
|
|
if err != nil {
|
|
t.Fatalf("CreateApplication failed: %v", err)
|
|
}
|
|
t.Logf("✅ Created application: %s", app.ID)
|
|
|
|
// 2. Update Status
|
|
statusReq := dto.UpdateApplicationStatusRequest{
|
|
Status: "reviewed",
|
|
Notes: ptrString("Looks good"),
|
|
}
|
|
updated, err := appSvc.UpdateApplicationStatus(app.ID, statusReq)
|
|
if err != nil {
|
|
t.Fatalf("UpdateApplicationStatus failed: %v", err)
|
|
}
|
|
if updated.Status != "reviewed" {
|
|
t.Errorf("Status mismatch: expected reviewed, got %s", updated.Status)
|
|
}
|
|
t.Log("✅ Updated application status")
|
|
|
|
// Cleanup: Delete Application
|
|
err = appSvc.DeleteApplication(app.ID)
|
|
if err != nil {
|
|
t.Fatalf("DeleteApplication failed: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestAdminService_Integration tests admin actions
|
|
func TestAdminService_Integration(t *testing.T) {
|
|
if os.Getenv("RUN_INTEGRATION_TESTS") != "true" {
|
|
t.Skip("Skipping integration test")
|
|
}
|
|
|
|
dbURL := os.Getenv("DATABASE_URL")
|
|
db, err := sql.Open("postgres", dbURL)
|
|
if err != nil {
|
|
t.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
adminSvc := services.NewAdminService(db)
|
|
|
|
// Need a company to modify
|
|
var companyID string
|
|
err = db.QueryRow("SELECT id FROM companies LIMIT 1").Scan(&companyID)
|
|
if err != nil {
|
|
t.Skip("No companies found")
|
|
}
|
|
|
|
t.Run("updates company verification status", func(t *testing.T) {
|
|
verified := true
|
|
active := true
|
|
req := dto.UpdateCompanyRequest{
|
|
Verified: &verified,
|
|
Active: &active,
|
|
}
|
|
|
|
updated, err := adminSvc.UpdateCompany(context.Background(), companyID, req)
|
|
if err != nil {
|
|
t.Fatalf("UpdateCompany failed: %v", err)
|
|
}
|
|
if !updated.Verified {
|
|
t.Errorf("Company should be verifying")
|
|
}
|
|
t.Logf("✅ Company verification updated")
|
|
})
|
|
|
|
// Test GetEmailSettings and others
|
|
t.Run("get email settings", func(t *testing.T) {
|
|
settings, err := adminSvc.GetEmailSettings(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("GetEmailSettings failed: %v", err)
|
|
}
|
|
if settings != nil {
|
|
t.Logf("✅ Got email settings: %s", settings.ID)
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestJobService_Filters_Integration tests complex job queries
|
|
func TestJobService_Filters_Integration(t *testing.T) {
|
|
if os.Getenv("RUN_INTEGRATION_TESTS") != "true" {
|
|
t.Skip("Skipping integration test")
|
|
}
|
|
|
|
dbURL := os.Getenv("DATABASE_URL")
|
|
db, err := sql.Open("postgres", dbURL)
|
|
if err != nil {
|
|
t.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
jobSvc := services.NewJobService(db)
|
|
|
|
t.Run("filters jobs by multiple criteria", func(t *testing.T) {
|
|
mode := "remote"
|
|
salaryMin := 1000.0
|
|
filter := dto.JobFilterQuery{
|
|
WorkMode: &mode,
|
|
SalaryMin: &salaryMin,
|
|
SortBy: ptrString("recent"),
|
|
}
|
|
filter.Limit = 5
|
|
|
|
jobs, _, err := jobSvc.GetJobs(filter)
|
|
if err != nil {
|
|
t.Fatalf("GetJobs with filter failed: %v", err)
|
|
}
|
|
t.Logf("✅ Listed %d jobs with filters", len(jobs))
|
|
})
|
|
}
|