diff --git a/backend/internal/services/admin_service_test.go b/backend/internal/services/admin_service_test.go index 963b9d5..47f6f94 100644 --- a/backend/internal/services/admin_service_test.go +++ b/backend/internal/services/admin_service_test.go @@ -163,10 +163,10 @@ func TestAdminService_GetUser(t *testing.T) { t.Run("returns user by id", func(t *testing.T) { userID := "019b5290-9680-7c06-9ee3-c9e0e117251b" now := time.Now() - mock.ExpectQuery("SELECT id, full_name, email, role, created_at FROM users WHERE id"). + mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, full_name, email, role, COALESCE(status, 'active'), created_at, phone, bio, avatar_url FROM users WHERE id = $1`)). WithArgs(userID). - WillReturnRows(sqlmock.NewRows([]string{"id", "name", "email", "role", "created_at"}). - AddRow(userID, "Test User", "test@example.com", "admin", now)) + WillReturnRows(sqlmock.NewRows([]string{"id", "full_name", "email", "role", "status", "created_at", "phone", "bio", "avatar_url"}). + AddRow(userID, "Test User", "test@example.com", "admin", "active", now, "123", "Bio", "url")) user, err := service.GetUser(ctx, userID) if err != nil { @@ -472,93 +472,92 @@ func TestIsActiveApplicationStatus(t *testing.T) { } } - // Tests for GetCompanyByUserID func TestAdminService_GetCompanyByUserID(t *testing.T) { -db, mock, err := sqlmock.New() -if err != nil { -t.Fatalf("Error: %v", err) -} -defer db.Close() + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("Error: %v", err) + } + defer db.Close() -svc := NewAdminService(db) -now := time.Now() + svc := NewAdminService(db) + now := time.Now() -// Exact query regex match -mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, name, slug, description, logo_url, website, location, active, verified, created_at, updated_at FROM companies WHERE user_id=$1`)). -WithArgs("user-1"). -WillReturnRows(sqlmock.NewRows([]string{ -"id", "name", "slug", "description", "logo_url", "website", "location", -"active", "verified", "created_at", "updated_at", -}).AddRow( -"comp-1", "Company", "company-slug", nil, nil, nil, nil, -true, true, now, now, -)) + // Exact query regex match + mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, name, slug, description, logo_url, website, location, active, verified, created_at, updated_at FROM companies WHERE user_id=$1`)). + WithArgs("user-1"). + WillReturnRows(sqlmock.NewRows([]string{ + "id", "name", "slug", "description", "logo_url", "website", "location", + "active", "verified", "created_at", "updated_at", + }).AddRow( + "comp-1", "Company", "company-slug", nil, nil, nil, nil, + true, true, now, now, + )) -// Note: implementation might be using "owner_id" or "user_id". Check failure if any. -_, err = svc.GetCompanyByUserID(context.Background(), "user-1") -if err != nil { -t.Logf("GetCompanyByUserID error (likely query mismatch): %v", err) -} + // Note: implementation might be using "owner_id" or "user_id". Check failure if any. + _, err = svc.GetCompanyByUserID(context.Background(), "user-1") + if err != nil { + t.Logf("GetCompanyByUserID error (likely query mismatch): %v", err) + } } func TestAdminService_DeleteCompanyBasic(t *testing.T) { -db, mock, err := sqlmock.New() -if err != nil { -t.Fatalf("Error: %v", err) -} -defer db.Close() + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("Error: %v", err) + } + defer db.Close() -svc := NewAdminService(db) + svc := NewAdminService(db) -// Expect GetCompanyByID check first -mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, name, slug, description, logo_url, website, location, active, verified, created_at, updated_at FROM companies WHERE id=$1`)). -WithArgs("comp-1"). -WillReturnRows(sqlmock.NewRows([]string{"id", "company_id", "name"}).AddRow("comp-1", "user-1", "Test Co")) + // Expect GetCompanyByID check first + mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, name, slug, description, logo_url, website, location, active, verified, created_at, updated_at FROM companies WHERE id=$1`)). + WithArgs("comp-1"). + WillReturnRows(sqlmock.NewRows([]string{"id", "company_id", "name"}).AddRow("comp-1", "user-1", "Test Co")) -mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM companies WHERE id=$1`)). -WithArgs("comp-1"). -WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM companies WHERE id=$1`)). + WithArgs("comp-1"). + WillReturnResult(sqlmock.NewResult(0, 1)) -_ = svc.DeleteCompany(context.Background(), "comp-1") + _ = svc.DeleteCompany(context.Background(), "comp-1") } func TestAdminService_DeleteEmailTemplateBasic(t *testing.T) { -db, mock, err := sqlmock.New() -if err != nil { -t.Fatalf("Error: %v", err) -} -defer db.Close() + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("Error: %v", err) + } + defer db.Close() -svc := NewAdminService(db) + svc := NewAdminService(db) -mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM email_templates WHERE slug=$1`)). -WithArgs("welcome"). -WillReturnResult(sqlmock.NewResult(0, 1)) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM email_templates WHERE slug=$1`)). + WithArgs("welcome"). + WillReturnResult(sqlmock.NewResult(0, 1)) -_ = svc.DeleteEmailTemplate(context.Background(), "welcome") + _ = svc.DeleteEmailTemplate(context.Background(), "welcome") } func TestAdminService_GetEmailSettingsBasic(t *testing.T) { -db, mock, err := sqlmock.New() -if err != nil { -t.Fatalf("Error: %v", err) -} -defer db.Close() + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("Error: %v", err) + } + defer db.Close() -svc := NewAdminService(db) + svc := NewAdminService(db) -query := `SELECT id, provider, smtp_host, smtp_port, smtp_user, smtp_pass, smtp_secure, sender_name, sender_email, amqp_url, is_active, updated_at + query := `SELECT id, provider, smtp_host, smtp_port, smtp_user, smtp_pass, smtp_secure, sender_name, sender_email, amqp_url, is_active, updated_at FROM email_settings WHERE is_active = true ORDER BY updated_at DESC LIMIT 1` -mock.ExpectQuery(regexp.QuoteMeta(query)). -WillReturnRows(sqlmock.NewRows([]string{"id", "provider", "smtp_host", "smtp_port", "smtp_user", "smtp_pass", "smtp_secure", "sender_name", "sender_email", "amqp_url", "is_active", "updated_at"}). -AddRow("1", "smtp", "smtp.test.com", 587, "user", "pass", false, "Sender", "sender@test.com", "amqp://", true, time.Now())) + mock.ExpectQuery(regexp.QuoteMeta(query)). + WillReturnRows(sqlmock.NewRows([]string{"id", "provider", "smtp_host", "smtp_port", "smtp_user", "smtp_pass", "smtp_secure", "sender_name", "sender_email", "amqp_url", "is_active", "updated_at"}). + AddRow("1", "smtp", "smtp.test.com", 587, "user", "pass", false, "Sender", "sender@test.com", "amqp://", true, time.Now())) -settings, err := svc.GetEmailSettings(context.Background()) -if err == nil { -if settings.SMTPHost == nil || *settings.SMTPHost != "smtp.test.com" { -t.Errorf("Expected smtp.test.com") -} -} + settings, err := svc.GetEmailSettings(context.Background()) + if err == nil { + if settings.SMTPHost == nil || *settings.SMTPHost != "smtp.test.com" { + t.Errorf("Expected smtp.test.com") + } + } } diff --git a/backend/internal/services/application_service_test.go b/backend/internal/services/application_service_test.go index fd2cd12..a50933d 100644 --- a/backend/internal/services/application_service_test.go +++ b/backend/internal/services/application_service_test.go @@ -126,7 +126,7 @@ func TestApplicationService_CreateApplication_Full(t *testing.T) { mock.ExpectQuery("INSERT INTO applications"). WithArgs( req.JobID, req.UserID, req.Name, req.Phone, req.LineID, req.WhatsApp, req.Email, - req.Message, req.ResumeURL, sqlmock.AnyArg(), "pending", sqlmock.AnyArg(), sqlmock.AnyArg(), + req.Message, req.ResumeURL, sqlmock.AnyArg(), sqlmock.AnyArg(), "pending", sqlmock.AnyArg(), sqlmock.AnyArg(), ). WillReturnRows(sqlmock.NewRows([]string{"id", "created_at", "updated_at"}). AddRow("app-789", time.Now(), time.Now())) @@ -156,10 +156,10 @@ func TestApplicationService_GetApplications(t *testing.T) { rows := sqlmock.NewRows([]string{ "id", "job_id", "user_id", "name", "phone", "line_id", "whatsapp", "email", - "message", "resume_url", "status", "created_at", "updated_at", + "message", "resume_url", "documents", "answers", "status", "created_at", "updated_at", }).AddRow( "app-1", jobID, "user-1", "John Doe", "123", nil, nil, "john@test.com", - "Hello", "http://resume.pdf", "pending", time.Now(), time.Now(), + "Hello", "http://resume.pdf", nil, nil, "pending", time.Now(), time.Now(), ) mock.ExpectQuery("SELECT id, job_id, user_id, name, phone, line_id, whatsapp, email"). @@ -191,10 +191,10 @@ func TestApplicationService_GetApplicationByID(t *testing.T) { rows := sqlmock.NewRows([]string{ "id", "job_id", "user_id", "name", "phone", "line_id", "whatsapp", "email", - "message", "resume_url", "documents", "status", "created_at", "updated_at", + "message", "resume_url", "documents", "answers", "status", "created_at", "updated_at", }).AddRow( appID, "job-1", "user-1", "Jane Doe", "456", nil, nil, "jane@test.com", - "Hi", "http://cv.pdf", nil, "pending", time.Now(), time.Now(), + "Hi", "http://cv.pdf", nil, nil, "pending", time.Now(), time.Now(), ) mock.ExpectQuery("SELECT id, job_id, user_id, name, phone, line_id, whatsapp, email"). diff --git a/backend/internal/services/email_service_test.go b/backend/internal/services/email_service_test.go index 2dce05d..022f7d7 100644 --- a/backend/internal/services/email_service_test.go +++ b/backend/internal/services/email_service_test.go @@ -21,13 +21,18 @@ func TestNewEmailService(t *testing.T) { } service := NewEmailService(db, credsSvc) - if service == nil { + concreteService, ok := service.(*emailServiceImpl) + if !ok { + t.Fatal("Expected NewEmailService to return *emailServiceImpl") + } + + if concreteService == nil { t.Error("Expected service, got nil") } - if service.db != db { + if concreteService.db != db { t.Error("Expected db to be set") } - if service.credentialsService != credsSvc { + if concreteService.credentialsService != credsSvc { t.Error("Expected credentialsService to be set") } } diff --git a/backend/internal/services/job_service.go b/backend/internal/services/job_service.go index 453dbe3..4796e9b 100644 --- a/backend/internal/services/job_service.go +++ b/backend/internal/services/job_service.go @@ -91,11 +91,12 @@ func (s *JobService) GetJobs(filter dto.JobFilterQuery) ([]models.JobWithCompany j.view_count, j.featured_until, (SELECT COUNT(*) FROM applications a WHERE a.job_id = j.id) as applications_count FROM jobs j - LEFT JOIN companies c ON j.company_id::text = c.id::text - LEFT JOIN states r ON j.region_id::text = r.id::text - LEFT JOIN cities ci ON j.city_id::text = ci.id::text + LEFT JOIN companies c ON j.company_id = c.id + LEFT JOIN states r ON j.region_id = r.id + LEFT JOIN cities ci ON j.city_id = ci.id WHERE 1=1` - countQuery := `SELECT COUNT(*) FROM jobs j LEFT JOIN companies c ON j.company_id::text = c.id::text WHERE 1=1` + + countQuery := `SELECT COUNT(*) FROM jobs j WHERE 1=1` var args []interface{} argId := 1 @@ -105,7 +106,11 @@ func (s *JobService) GetJobs(filter dto.JobFilterQuery) ([]models.JobWithCompany searchTerm := fmt.Sprintf("%%%s%%", *filter.Search) clause := fmt.Sprintf(" AND (j.title ILIKE $%d OR j.description ILIKE $%d OR c.name ILIKE $%d)", argId, argId, argId) baseQuery += clause - countQuery += clause + + // Se tem busca textual que checa c.name, a query de count *precisa* do JOIN + countQueryBase := `SELECT COUNT(*) FROM jobs j LEFT JOIN companies c ON j.company_id = c.id WHERE 1=1` // Usando count interno modificado apenas se tiver c.name + countQuery = countQueryBase + clause + args = append(args, searchTerm) argId++ } diff --git a/backend/internal/services/job_service_test.go b/backend/internal/services/job_service_test.go index 3573fff..601db3c 100644 --- a/backend/internal/services/job_service_test.go +++ b/backend/internal/services/job_service_test.go @@ -35,8 +35,8 @@ func TestCreateJob(t *testing.T) { }, mockRun: func() { mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO jobs`)). - WithArgs("1", "user-123", "Go Developer", sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), "published", sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()). - WillReturnRows(sqlmock.NewRows([]string{"id", "created_at", "updated_at"}).AddRow("100", time.Now(), time.Now())) + WithArgs("1", "user-123", "Go Developer", sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), "published", sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()). + WillReturnRows(sqlmock.NewRows([]string{"id", "date_posted", "created_at", "updated_at"}).AddRow("100", time.Now(), time.Now(), time.Now())) }, wantErr: false, }, @@ -93,17 +93,26 @@ func TestGetJobs(t *testing.T) { // List query mock.ExpectQuery(regexp.QuoteMeta(`SELECT j.id, j.company_id, j.title, j.description, j.salary_min, j.salary_max, j.salary_type, - j.employment_type, j.work_mode, j.working_hours, j.location, j.status, j.salary_negotiable, j.is_featured, j.created_at, j.updated_at, - COALESCE(c.name, '') as company_name, c.logo_url as company_logo_url, - r.name as region_name, ci.name as city_name - FROM jobs j`)). + j.employment_type, j.work_mode, j.working_hours, j.location, j.status, j.salary_negotiable, j.is_featured, COALESCE(j.date_posted, j.created_at) AS date_posted, j.created_at, j.updated_at, + CASE + WHEN c.type = 'CANDIDATE_WORKSPACE' OR c.name LIKE 'Candidate - %' THEN '' + ELSE COALESCE(c.name, '') + END as company_name, c.logo_url as company_logo_url, + r.name as region_name, ci.name as city_name, + j.view_count, j.featured_until, + (SELECT COUNT(*) FROM applications a WHERE a.job_id = j.id) as applications_count + FROM jobs j + LEFT JOIN companies c ON j.company_id = c.id + LEFT JOIN states r ON j.region_id = r.id + LEFT JOIN cities ci ON j.city_id = ci.id + WHERE 1=1`)). WillReturnRows(sqlmock.NewRows([]string{ "id", "company_id", "title", "description", "salary_min", "salary_max", "salary_type", - "employment_type", "work_mode", "working_hours", "location", "status", "salary_negotiable", "is_featured", "created_at", "updated_at", - "company_name", "company_logo_url", "region_name", "city_name", + "employment_type", "work_mode", "working_hours", "location", "status", "salary_negotiable", "is_featured", "date_posted", "created_at", "updated_at", + "company_name", "company_logo_url", "region_name", "city_name", "view_count", "featured_until", "applications_count", }).AddRow( - "1", "10", "Dev", "Desc", 100, 200, "m", "ft", "Remote", "40h", "Remote", "open", true, false, time.Now(), time.Now(), - "Acme", "url", "Region", "City", + "1", "10", "Dev", "Desc", 100, 200, "m", "ft", "Remote", "40h", "Remote", "open", true, false, time.Now(), time.Now(), time.Now(), + "Acme", "url", "Region", "City", 0, nil, 0, )) // Count query @@ -136,15 +145,16 @@ func TestGetJobByID(t *testing.T) { jobID := "100" - // Mocking row for GetJobByID (20 columns) rows := sqlmock.NewRows([]string{ "id", "company_id", "title", "description", "salary_min", "salary_max", "salary_type", - "employment_type", "working_hours", "location", "region_id", "city_id", "salary_negotiable", - "requirements", "benefits", "visa_support", "language_level", "status", "created_at", "updated_at", + "employment_type", "working_hours", "location", "region_id", "city_id", + "requirements", "benefits", "visa_support", "language_level", "status", "is_featured", "featured_until", "view_count", "date_posted", "created_at", "updated_at", + "salary_negotiable", "currency", "work_mode", }).AddRow( jobID, 1, "Title", "Desc", 100, 200, "m", - "ft", "40h", "Remote", 0, 0, false, - nil, nil, false, "N2", "open", time.Now(), time.Now(), + "ft", "40h", "Remote", 0, 0, + nil, nil, false, "N2", "open", false, nil, 0, time.Now(), time.Now(), time.Now(), + false, "USD", "remote", ) mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, company_id`)). @@ -181,15 +191,16 @@ func TestUpdateJob(t *testing.T) { WithArgs(newTitle, jobID). WillReturnRows(sqlmock.NewRows([]string{"id", "updated_at"}).AddRow(jobID, time.Now())) - // Expect subsequent SELECT to fetch updated job (20 columns) rows := sqlmock.NewRows([]string{ "id", "company_id", "title", "description", "salary_min", "salary_max", "salary_type", - "employment_type", "working_hours", "location", "region_id", "city_id", "salary_negotiable", - "requirements", "benefits", "visa_support", "language_level", "status", "created_at", "updated_at", + "employment_type", "working_hours", "location", "region_id", "city_id", + "requirements", "benefits", "visa_support", "language_level", "status", "is_featured", "featured_until", "view_count", "date_posted", "created_at", "updated_at", + "salary_negotiable", "currency", "work_mode", }).AddRow( jobID, 1, newTitle, "Desc", 100, 200, "m", - "ft", "40h", "Remote", 0, 0, false, - nil, nil, false, "N2", "open", time.Now(), time.Now(), + "ft", "40h", "Remote", 0, 0, + nil, nil, false, "N2", "open", false, nil, 0, time.Now(), time.Now(), time.Now(), + false, "USD", "remote", ) mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, company_id`)). diff --git a/backend/internal/services/test_out.txt b/backend/internal/services/test_out.txt new file mode 100644 index 0000000..d0a7d06 --- /dev/null +++ b/backend/internal/services/test_out.txt @@ -0,0 +1,265 @@ +=== RUN TestAdminService_ListCompanies +=== RUN TestAdminService_ListCompanies/returns_empty_list_when_no_companies +=== RUN TestAdminService_ListCompanies/filters_by_verified_status +--- PASS: TestAdminService_ListCompanies (0.00s) + --- PASS: TestAdminService_ListCompanies/returns_empty_list_when_no_companies (0.00s) + --- PASS: TestAdminService_ListCompanies/filters_by_verified_status (0.00s) +=== RUN TestAdminService_ListTags +=== RUN TestAdminService_ListTags/returns_empty_list_when_no_tags +=== RUN TestAdminService_ListTags/filters_by_category +--- PASS: TestAdminService_ListTags (0.00s) + --- PASS: TestAdminService_ListTags/returns_empty_list_when_no_tags (0.00s) + --- PASS: TestAdminService_ListTags/filters_by_category (0.00s) +=== RUN TestAdminService_CreateTag +=== RUN TestAdminService_CreateTag/creates_a_new_tag +=== RUN TestAdminService_CreateTag/rejects_empty_tag_name +--- PASS: TestAdminService_CreateTag (0.00s) + --- PASS: TestAdminService_CreateTag/creates_a_new_tag (0.00s) + --- PASS: TestAdminService_CreateTag/rejects_empty_tag_name (0.00s) +=== RUN TestAdminService_GetUser +=== RUN TestAdminService_GetUser/returns_user_by_id + admin_service_test.go:173: Unexpected error: Query: could not match actual sql: "SELECT id, full_name, email, role, COALESCE(status, 'active'), created_at, phone, bio, avatar_url FROM users WHERE id = $1" with expected regexp "SELECT id, full_name, email, role, created_at FROM users WHERE id" +--- FAIL: TestAdminService_GetUser (0.00s) + --- FAIL: TestAdminService_GetUser/returns_user_by_id (0.00s) +=== RUN TestAdminService_UpdateCompanyStatus +=== RUN TestAdminService_UpdateCompanyStatus/updates_active_status_successfully +=== RUN TestAdminService_UpdateCompanyStatus/updates_verified_status_successfully +=== RUN TestAdminService_UpdateCompanyStatus/returns_error_when_company_not_found +--- PASS: TestAdminService_UpdateCompanyStatus (0.00s) + --- PASS: TestAdminService_UpdateCompanyStatus/updates_active_status_successfully (0.00s) + --- PASS: TestAdminService_UpdateCompanyStatus/updates_verified_status_successfully (0.00s) + --- PASS: TestAdminService_UpdateCompanyStatus/returns_error_when_company_not_found (0.00s) +=== RUN TestAdminService_ListUsers +=== RUN TestAdminService_ListUsers/returns_users_list +--- PASS: TestAdminService_ListUsers (0.00s) + --- PASS: TestAdminService_ListUsers/returns_users_list (0.00s) +=== RUN TestAdminService_DuplicateJob +--- PASS: TestAdminService_DuplicateJob (0.00s) +=== RUN TestAdminService_EmailTemplates +=== RUN TestAdminService_EmailTemplates/ListEmailTemplates +=== RUN TestAdminService_EmailTemplates/CreateEmailTemplate +--- PASS: TestAdminService_EmailTemplates (0.00s) + --- PASS: TestAdminService_EmailTemplates/ListEmailTemplates (0.00s) + --- PASS: TestAdminService_EmailTemplates/CreateEmailTemplate (0.00s) +=== RUN TestStringOrNil +=== RUN TestStringOrNil/nil_for_invalid +=== RUN TestStringOrNil/pointer_for_valid +--- PASS: TestStringOrNil (0.00s) + --- PASS: TestStringOrNil/nil_for_invalid (0.00s) + --- PASS: TestStringOrNil/pointer_for_valid (0.00s) +=== RUN TestBuildLocation +=== RUN TestBuildLocation/nil_when_both_empty +=== RUN TestBuildLocation/city,_state_format +=== RUN TestBuildLocation/city_only +--- PASS: TestBuildLocation (0.00s) + --- PASS: TestBuildLocation/nil_when_both_empty (0.00s) + --- PASS: TestBuildLocation/city,_state_format (0.00s) + --- PASS: TestBuildLocation/city_only (0.00s) +=== RUN TestNormalizeSkills +--- PASS: TestNormalizeSkills (0.00s) +=== RUN TestIsActiveApplicationStatus +--- PASS: TestIsActiveApplicationStatus (0.00s) +=== RUN TestAdminService_GetCompanyByUserID + admin_service_test.go:501: GetCompanyByUserID error (likely query mismatch): Query: could not match actual sql: "SELECT id, full_name, email, role, COALESCE(status, 'active'), created_at, phone, bio, avatar_url FROM users WHERE id = $1" with expected regexp "SELECT id, name, slug, description, logo_url, website, location, active, verified, created_at, updated_at FROM companies WHERE user_id=\$1" +--- PASS: TestAdminService_GetCompanyByUserID (0.00s) +=== RUN TestAdminService_DeleteCompanyBasic +--- PASS: TestAdminService_DeleteCompanyBasic (0.00s) +=== RUN TestAdminService_DeleteEmailTemplateBasic +--- PASS: TestAdminService_DeleteEmailTemplateBasic (0.00s) +=== RUN TestAdminService_GetEmailSettingsBasic +--- PASS: TestAdminService_GetEmailSettingsBasic (0.00s) +=== RUN TestAuditService_RecordLogin +=== RUN TestAuditService_RecordLogin/records_login_successfully +=== RUN TestAuditService_RecordLogin/records_login_without_optional_fields +--- PASS: TestAuditService_RecordLogin (0.00s) + --- PASS: TestAuditService_RecordLogin/records_login_successfully (0.00s) + --- PASS: TestAuditService_RecordLogin/records_login_without_optional_fields (0.00s) +=== RUN TestAuditService_ListLogins +=== RUN TestAuditService_ListLogins/returns_empty_list_when_no_audits +=== RUN TestAuditService_ListLogins/respects_custom_limit +--- PASS: TestAuditService_ListLogins (0.00s) + --- PASS: TestAuditService_ListLogins/returns_empty_list_when_no_audits (0.00s) + --- PASS: TestAuditService_ListLogins/respects_custom_limit (0.00s) +=== RUN TestNewChatService +--- PASS: TestNewChatService (0.00s) +=== RUN TestChatService_SendMessage + chat_service_test.go:37: Skipping due to async goroutine requiring real dependencies +--- SKIP: TestChatService_SendMessage (0.00s) +=== RUN TestChatService_ListMessages +=== RUN TestChatService_ListMessages/returns_messages_list +=== RUN TestChatService_ListMessages/returns_empty_list_when_no_messages +--- PASS: TestChatService_ListMessages (0.00s) + --- PASS: TestChatService_ListMessages/returns_messages_list (0.00s) + --- PASS: TestChatService_ListMessages/returns_empty_list_when_no_messages (0.00s) +=== RUN TestChatService_ListConversations +=== RUN TestChatService_ListConversations/lists_conversations_for_candidate +=== RUN TestChatService_ListConversations/lists_conversations_for_company +=== RUN TestChatService_ListConversations/returns_error_for_invalid_context +--- PASS: TestChatService_ListConversations (0.00s) + --- PASS: TestChatService_ListConversations/lists_conversations_for_candidate (0.00s) + --- PASS: TestChatService_ListConversations/lists_conversations_for_company (0.00s) + --- PASS: TestChatService_ListConversations/returns_error_for_invalid_context (0.00s) +=== RUN TestMessage_Struct +--- PASS: TestMessage_Struct (0.00s) +=== RUN TestConversation_Struct +--- PASS: TestConversation_Struct (0.00s) +=== RUN TestNewEmailService +--- PASS: TestNewEmailService (0.00s) +=== RUN TestEmailService_SendTemplateEmail_NoAMQPURL +=== RUN TestEmailService_SendTemplateEmail_NoAMQPURL/returns_error_when_AMQP_URL_not_configured +--- PASS: TestEmailService_SendTemplateEmail_NoAMQPURL (0.00s) + --- PASS: TestEmailService_SendTemplateEmail_NoAMQPURL/returns_error_when_AMQP_URL_not_configured (0.00s) +=== RUN TestEmailService_SendTemplateEmail_DBError +=== RUN TestEmailService_SendTemplateEmail_DBError/handles_db_error_gracefully +2026/02/25 06:02:13 [EmailService] Failed to fetch AMQP URL: sql: connection is already closed +--- PASS: TestEmailService_SendTemplateEmail_DBError (0.00s) + --- PASS: TestEmailService_SendTemplateEmail_DBError/handles_db_error_gracefully (0.00s) +=== RUN TestEmailJob_Struct +--- PASS: TestEmailJob_Struct (0.00s) +=== RUN TestNotificationService_ListNotifications +=== RUN TestNotificationService_ListNotifications/returns_empty_list_when_no_notifications +--- PASS: TestNotificationService_ListNotifications (0.00s) + --- PASS: TestNotificationService_ListNotifications/returns_empty_list_when_no_notifications (0.00s) +=== RUN TestNotificationService_CreateNotification +=== RUN TestNotificationService_CreateNotification/creates_a_new_notification +--- PASS: TestNotificationService_CreateNotification (0.00s) + --- PASS: TestNotificationService_CreateNotification/creates_a_new_notification (0.00s) +=== RUN TestNotificationService_MarkAsRead +=== RUN TestNotificationService_MarkAsRead/marks_notification_as_read +--- PASS: TestNotificationService_MarkAsRead (0.00s) + --- PASS: TestNotificationService_MarkAsRead/marks_notification_as_read (0.00s) +=== RUN TestNotificationService_MarkAllAsRead +=== RUN TestNotificationService_MarkAllAsRead/marks_all_notifications_as_read +--- PASS: TestNotificationService_MarkAllAsRead (0.00s) + --- PASS: TestNotificationService_MarkAllAsRead/marks_all_notifications_as_read (0.00s) +=== RUN TestNewNotificationService +--- PASS: TestNewNotificationService (0.00s) +=== RUN TestNewSettingsService +--- PASS: TestNewSettingsService (0.00s) +=== RUN TestSettingsService_GetSettings +=== RUN TestSettingsService_GetSettings/returns_setting_value +=== RUN TestSettingsService_GetSettings/returns_nil_when_not_found +=== RUN TestSettingsService_GetSettings/returns_error_on_db_failure +--- PASS: TestSettingsService_GetSettings (0.00s) + --- PASS: TestSettingsService_GetSettings/returns_setting_value (0.00s) + --- PASS: TestSettingsService_GetSettings/returns_nil_when_not_found (0.00s) + --- PASS: TestSettingsService_GetSettings/returns_error_on_db_failure (0.00s) +=== RUN TestSettingsService_SaveSettings +=== RUN TestSettingsService_SaveSettings/saves_setting_successfully +=== RUN TestSettingsService_SaveSettings/upserts_existing_setting +=== RUN TestSettingsService_SaveSettings/returns_error_on_db_failure +=== RUN TestSettingsService_SaveSettings/handles_unmarshalable_value +--- PASS: TestSettingsService_SaveSettings (0.00s) + --- PASS: TestSettingsService_SaveSettings/saves_setting_successfully (0.00s) + --- PASS: TestSettingsService_SaveSettings/upserts_existing_setting (0.00s) + --- PASS: TestSettingsService_SaveSettings/returns_error_on_db_failure (0.00s) + --- PASS: TestSettingsService_SaveSettings/handles_unmarshalable_value (0.00s) +=== RUN TestNewStorageService +--- PASS: TestNewStorageService (0.00s) +=== RUN TestUploadConfig_Validation +--- PASS: TestUploadConfig_Validation (0.00s) +=== RUN TestUploadConfig_DefaultRegion +--- PASS: TestUploadConfig_DefaultRegion (0.00s) +=== RUN TestUploadConfig_IncompleteFields + storage_service_test.go:90: Correctly identified incomplete credentials +--- PASS: TestUploadConfig_IncompleteFields (0.00s) +=== RUN TestAdminService_Extra_Unit +--- PASS: TestAdminService_Extra_Unit (0.00s) +=== RUN TestAdminService_DuplicateJob +--- PASS: TestAdminService_DuplicateJob (0.00s) +=== RUN TestAdminService_ListMethods +--- PASS: TestAdminService_ListMethods (0.00s) +=== RUN TestCreateApplication_Success +--- PASS: TestCreateApplication_Success (0.10s) +=== RUN TestApplicationService_DeleteApplication +--- PASS: TestApplicationService_DeleteApplication (0.00s) +=== RUN TestApplicationService_CreateApplication_Full + application_service_test.go:136: CreateApplication failed: Query ' + INSERT INTO applications ( + job_id, user_id, name, phone, line_id, whatsapp, email, + message, resume_url, documents, answers, status, created_at, updated_at + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) + RETURNING id, created_at, updated_at + ', arguments do not match: expected 13, but got 14 arguments +--- FAIL: TestApplicationService_CreateApplication_Full (0.00s) +=== RUN TestApplicationService_GetApplications + application_service_test.go:171: GetApplications failed: sql: expected 13 destination arguments in Scan, not 15 +--- FAIL: TestApplicationService_GetApplications (0.00s) +=== RUN TestApplicationService_GetApplicationByID + application_service_test.go:206: GetApplicationByID failed: sql: expected 14 destination arguments in Scan, not 15 +--- FAIL: TestApplicationService_GetApplicationByID (0.00s) +=== RUN TestAuxiliaryServices_WithMockDB +--- PASS: TestAuxiliaryServices_WithMockDB (0.00s) +=== RUN TestLocationService_WithMockRepo +--- PASS: TestLocationService_WithMockRepo (0.00s) +=== RUN TestNotificationService_SaveFCMToken +--- PASS: TestNotificationService_SaveFCMToken (0.00s) +=== RUN TestChatService_Constructors +--- PASS: TestChatService_Constructors (0.00s) +=== RUN TestSaveCredentials +--- PASS: TestSaveCredentials (0.00s) +=== RUN TestGetDecryptedKey +--- PASS: TestGetDecryptedKey (0.48s) +=== RUN TestListConfiguredServices +--- PASS: TestListConfiguredServices (0.00s) +=== RUN TestDeleteCredentials +--- PASS: TestDeleteCredentials (0.00s) +=== RUN TestEncryptPayload +--- PASS: TestEncryptPayload (0.39s) +=== RUN TestCreateJob +=== RUN TestCreateJob/Success +[JOB_SERVICE DEBUG] === CreateJob Started === +[JOB_SERVICE DEBUG] CompanyID=1, CreatedBy=user-123, Title=Go Developer, Status=published +[JOB_SERVICE DEBUG] Executing INSERT query... +[JOB_SERVICE DEBUG] Job struct: &{ID: CompanyID:1 CreatedBy:user-123 Title:Go Developer Description: SalaryMin: SalaryMax: SalaryType: Currency: SalaryNegotiable:false EmploymentType: WorkMode: WorkingHours: Location: RegionID: CityID: Requirements:map[] Benefits:map[] Questions:map[] VisaSupport:false LanguageLevel: Status:published IsFeatured:false ViewCount:0 FeaturedUntil: DatePosted:2026-02-25 06:02:14.4861294 -0600 CST m=+1.036940001 CreatedAt:2026-02-25 06:02:14.4861294 -0600 CST m=+1.036940001 UpdatedAt:2026-02-25 06:02:14.4861294 -0600 CST m=+1.036940001} +[JOB_SERVICE ERROR] INSERT query failed: Query ' + INSERT INTO jobs ( + company_id, created_by, title, description, salary_min, salary_max, salary_type, currency, + employment_type, work_mode, working_hours, location, region_id, city_id, + requirements, benefits, questions, visa_support, language_level, status, date_posted, created_at, updated_at, salary_negotiable + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24) + RETURNING id, date_posted, created_at, updated_at + ', arguments do not match: expected 21, but got 24 arguments + job_service_test.go:62: JobService.CreateJob() error = Query ' + INSERT INTO jobs ( + company_id, created_by, title, description, salary_min, salary_max, salary_type, currency, + employment_type, work_mode, working_hours, location, region_id, city_id, + requirements, benefits, questions, visa_support, language_level, status, date_posted, created_at, updated_at, salary_negotiable + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24) + RETURNING id, date_posted, created_at, updated_at + ', arguments do not match: expected 21, but got 24 arguments, wantErr false +=== RUN TestCreateJob/DB_Error +[JOB_SERVICE DEBUG] === CreateJob Started === +[JOB_SERVICE DEBUG] CompanyID=1, CreatedBy=user-123, Title=Go Developer, Status= +[JOB_SERVICE DEBUG] Executing INSERT query... +[JOB_SERVICE DEBUG] Job struct: &{ID: CompanyID:1 CreatedBy:user-123 Title:Go Developer Description: SalaryMin: SalaryMax: SalaryType: Currency: SalaryNegotiable:false EmploymentType: WorkMode: WorkingHours: Location: RegionID: CityID: Requirements:map[] Benefits:map[] Questions:map[] VisaSupport:false LanguageLevel: Status: IsFeatured:false ViewCount:0 FeaturedUntil: DatePosted:2026-02-25 06:02:14.4879161 -0600 CST m=+1.038726701 CreatedAt:2026-02-25 06:02:14.4879161 -0600 CST m=+1.038726701 UpdatedAt:2026-02-25 06:02:14.4879161 -0600 CST m=+1.038726701} +[JOB_SERVICE ERROR] INSERT query failed: Query ' + INSERT INTO jobs ( + company_id, created_by, title, description, salary_min, salary_max, salary_type, currency, + employment_type, work_mode, working_hours, location, region_id, city_id, + requirements, benefits, questions, visa_support, language_level, status, date_posted, created_at, updated_at, salary_negotiable + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24) + RETURNING id, date_posted, created_at, updated_at + ', arguments do not match: expected 21, but got 24 arguments +--- FAIL: TestCreateJob (0.00s) + --- FAIL: TestCreateJob/Success (0.00s) + --- PASS: TestCreateJob/DB_Error (0.00s) +=== RUN TestGetJobs +=== RUN TestGetJobs/List_All +--- PASS: TestGetJobs (0.00s) + --- PASS: TestGetJobs/List_All (0.00s) +=== RUN TestGetJobByID + job_service_test.go:165: GetJobByID() error = sql: expected 20 destination arguments in Scan, not 26 +--- FAIL: TestGetJobByID (0.00s) +=== RUN TestUpdateJob + job_service_test.go:210: UpdateJob() error = sql: expected 20 destination arguments in Scan, not 26 +--- FAIL: TestUpdateJob (0.00s) +=== RUN TestDeleteJob +--- PASS: TestDeleteJob (0.00s) +=== RUN TestTicketService_CRUD +--- PASS: TestTicketService_CRUD (0.00s) +=== RUN TestTicketService_Extended +--- PASS: TestTicketService_Extended (0.00s) +FAIL +FAIL github.com/rede5/gohorsejobs/backend/internal/services 1.143s +FAIL diff --git a/backend/internal/services/test_out_new.txt b/backend/internal/services/test_out_new.txt new file mode 100644 index 0000000..8e500a9 --- /dev/null +++ b/backend/internal/services/test_out_new.txt @@ -0,0 +1,57 @@ +--- FAIL: TestAdminService_GetUser (0.00s) + --- FAIL: TestAdminService_GetUser/returns_user_by_id (0.00s) + admin_service_test.go:173: Unexpected error: Query: could not match actual sql: "SELECT id, full_name, email, role, COALESCE(status, 'active'), created_at, phone, bio, avatar_url FROM users WHERE id = $1" with expected regexp "SELECT id, full_name, email, role, created_at FROM users WHERE id" +2026/02/25 06:11:52 [EmailService] Failed to fetch AMQP URL: sql: connection is already closed +--- FAIL: TestApplicationService_CreateApplication_Full (0.00s) + application_service_test.go:136: CreateApplication failed: Query ' + INSERT INTO applications ( + job_id, user_id, name, phone, line_id, whatsapp, email, + message, resume_url, documents, answers, status, created_at, updated_at + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) + RETURNING id, created_at, updated_at + ', arguments do not match: expected 13, but got 14 arguments +--- FAIL: TestApplicationService_GetApplications (0.00s) + application_service_test.go:171: GetApplications failed: sql: expected 13 destination arguments in Scan, not 15 +--- FAIL: TestApplicationService_GetApplicationByID (0.00s) + application_service_test.go:206: GetApplicationByID failed: sql: expected 14 destination arguments in Scan, not 15 +[JOB_SERVICE DEBUG] === CreateJob Started === +[JOB_SERVICE DEBUG] CompanyID=1, CreatedBy=user-123, Title=Go Developer, Status=published +[JOB_SERVICE DEBUG] Executing INSERT query... +[JOB_SERVICE DEBUG] Job struct: &{ID: CompanyID:1 CreatedBy:user-123 Title:Go Developer Description: SalaryMin: SalaryMax: SalaryType: Currency: SalaryNegotiable:false EmploymentType: WorkMode: WorkingHours: Location: RegionID: CityID: Requirements:map[] Benefits:map[] Questions:map[] VisaSupport:false LanguageLevel: Status:published IsFeatured:false ViewCount:0 FeaturedUntil: DatePosted:2026-02-25 06:11:52.3397837 -0600 CST m=+0.337955101 CreatedAt:2026-02-25 06:11:52.3397837 -0600 CST m=+0.337955101 UpdatedAt:2026-02-25 06:11:52.3397837 -0600 CST m=+0.337955101} +[JOB_SERVICE ERROR] INSERT query failed: Query ' + INSERT INTO jobs ( + company_id, created_by, title, description, salary_min, salary_max, salary_type, currency, + employment_type, work_mode, working_hours, location, region_id, city_id, + requirements, benefits, questions, visa_support, language_level, status, date_posted, created_at, updated_at, salary_negotiable + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24) + RETURNING id, date_posted, created_at, updated_at + ', arguments do not match: expected 21, but got 24 arguments +[JOB_SERVICE DEBUG] === CreateJob Started === +[JOB_SERVICE DEBUG] CompanyID=1, CreatedBy=user-123, Title=Go Developer, Status= +[JOB_SERVICE DEBUG] Executing INSERT query... +[JOB_SERVICE DEBUG] Job struct: &{ID: CompanyID:1 CreatedBy:user-123 Title:Go Developer Description: SalaryMin: SalaryMax: SalaryType: Currency: SalaryNegotiable:false EmploymentType: WorkMode: WorkingHours: Location: RegionID: CityID: Requirements:map[] Benefits:map[] Questions:map[] VisaSupport:false LanguageLevel: Status: IsFeatured:false ViewCount:0 FeaturedUntil: DatePosted:2026-02-25 06:11:52.3397837 -0600 CST m=+0.337955101 CreatedAt:2026-02-25 06:11:52.3397837 -0600 CST m=+0.337955101 UpdatedAt:2026-02-25 06:11:52.3397837 -0600 CST m=+0.337955101} +[JOB_SERVICE ERROR] INSERT query failed: Query ' + INSERT INTO jobs ( + company_id, created_by, title, description, salary_min, salary_max, salary_type, currency, + employment_type, work_mode, working_hours, location, region_id, city_id, + requirements, benefits, questions, visa_support, language_level, status, date_posted, created_at, updated_at, salary_negotiable + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24) + RETURNING id, date_posted, created_at, updated_at + ', arguments do not match: expected 21, but got 24 arguments +--- FAIL: TestCreateJob (0.00s) + --- FAIL: TestCreateJob/Success (0.00s) + job_service_test.go:62: JobService.CreateJob() error = Query ' + INSERT INTO jobs ( + company_id, created_by, title, description, salary_min, salary_max, salary_type, currency, + employment_type, work_mode, working_hours, location, region_id, city_id, + requirements, benefits, questions, visa_support, language_level, status, date_posted, created_at, updated_at, salary_negotiable + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24) + RETURNING id, date_posted, created_at, updated_at + ', arguments do not match: expected 21, but got 24 arguments, wantErr false +--- FAIL: TestGetJobByID (0.00s) + job_service_test.go:165: GetJobByID() error = sql: expected 20 destination arguments in Scan, not 26 +--- FAIL: TestUpdateJob (0.00s) + job_service_test.go:210: UpdateJob() error = sql: expected 20 destination arguments in Scan, not 26 +FAIL +FAIL github.com/rede5/gohorsejobs/backend/internal/services 0.441s +FAIL diff --git a/backend/migration_error_output.txt b/backend/migration_error_output.txt new file mode 100644 index 0000000..8f9d059 --- /dev/null +++ b/backend/migration_error_output.txt @@ -0,0 +1,152 @@ +2026/02/25 06:54:46 Current Working Directory: C:\dev\gohorsejobs\backend +2026/02/25 06:54:46 Processing migration: 000_init_uuid_v7.sql (from migrations/000_init_uuid_v7.sql) +2026/02/25 06:54:46 Executing migration file migrations/000_init_uuid_v7.sql +2026/02/25 06:54:46 Migration 000_init_uuid_v7.sql applied successfully +2026/02/25 06:54:46 Processing migration: 001_create_users_table.sql (from migrations/001_create_users_table.sql) +2026/02/25 06:54:46 Executing migration file migrations/001_create_users_table.sql +2026/02/25 06:54:46 Warning while applying 001_create_users_table.sql: pq: relation "idx_users_identifier" already exists +2026/02/25 06:54:46 Processing migration: 002_create_companies_table.sql (from migrations/002_create_companies_table.sql) +2026/02/25 06:54:46 Executing migration file migrations/002_create_companies_table.sql +2026/02/25 06:54:46 Warning while applying 002_create_companies_table.sql: pq: relation "idx_companies_active" already exists +2026/02/25 06:54:46 Processing migration: 003_create_user_companies_table.sql (from migrations/003_create_user_companies_table.sql) +2026/02/25 06:54:46 Executing migration file migrations/003_create_user_companies_table.sql +2026/02/25 06:54:46 Warning while applying 003_create_user_companies_table.sql: pq: relation "idx_user_companies_user" already exists +2026/02/25 06:54:46 Processing migration: 004_create_prefectures_cities_tables.sql (from migrations/004_create_prefectures_cities_tables.sql) +2026/02/25 06:54:46 Executing migration file migrations/004_create_prefectures_cities_tables.sql +2026/02/25 06:54:46 Migration 004_create_prefectures_cities_tables.sql applied successfully +2026/02/25 06:54:46 Processing migration: 005_create_jobs_table.sql (from migrations/005_create_jobs_table.sql) +2026/02/25 06:54:46 Executing migration file migrations/005_create_jobs_table.sql +2026/02/25 06:54:46 Warning while applying 005_create_jobs_table.sql: pq: relation "idx_jobs_company" already exists +2026/02/25 06:54:46 Processing migration: 006_create_applications_table.sql (from migrations/006_create_applications_table.sql) +2026/02/25 06:54:46 Executing migration file migrations/006_create_applications_table.sql +2026/02/25 06:54:46 Warning while applying 006_create_applications_table.sql: pq: relation "idx_applications_job" already exists +2026/02/25 06:54:46 Processing migration: 007_create_favorite_jobs_table.sql (from migrations/007_create_favorite_jobs_table.sql) +2026/02/25 06:54:46 Executing migration file migrations/007_create_favorite_jobs_table.sql +2026/02/25 06:54:46 Warning while applying 007_create_favorite_jobs_table.sql: pq: relation "idx_favorite_jobs_user" already exists +2026/02/25 06:54:46 Processing migration: 008_create_password_resets_table.sql (from migrations/008_create_password_resets_table.sql) +2026/02/25 06:54:46 Executing migration file migrations/008_create_password_resets_table.sql +2026/02/25 06:54:46 Warning while applying 008_create_password_resets_table.sql: pq: relation "idx_password_resets_token" already exists +2026/02/25 06:54:46 Processing migration: 009_unify_schema.sql (from migrations/009_unify_schema.sql) +2026/02/25 06:54:46 Executing migration file migrations/009_unify_schema.sql +2026/02/25 06:54:46 Migration 009_unify_schema.sql applied successfully +2026/02/25 06:54:46 Processing migration: 010_seed_super_admin.sql (from migrations/010_seed_super_admin.sql) +2026/02/25 06:54:46 Executing migration file migrations/010_seed_super_admin.sql +2026/02/25 06:54:47 Migration 010_seed_super_admin.sql applied successfully +2026/02/25 06:54:47 Processing migration: 011_add_is_featured_to_jobs.sql (from migrations/011_add_is_featured_to_jobs.sql) +2026/02/25 06:54:47 Executing migration file migrations/011_add_is_featured_to_jobs.sql +2026/02/25 06:54:47 Migration 011_add_is_featured_to_jobs.sql applied successfully +2026/02/25 06:54:47 Processing migration: 012_add_work_mode.sql (from migrations/012_add_work_mode.sql) +2026/02/25 06:54:47 Executing migration file migrations/012_add_work_mode.sql +2026/02/25 06:54:47 Migration 012_add_work_mode.sql applied successfully +2026/02/25 06:54:47 Processing migration: 013_create_backoffice_tables.sql (from migrations/013_create_backoffice_tables.sql) +2026/02/25 06:54:47 Executing migration file migrations/013_create_backoffice_tables.sql +2026/02/25 06:54:47 Migration 013_create_backoffice_tables.sql applied successfully +2026/02/25 06:54:47 Processing migration: 014_update_job_status_constraint.sql (from migrations/014_update_job_status_constraint.sql) +2026/02/25 06:54:47 Executing migration file migrations/014_update_job_status_constraint.sql +2026/02/25 06:54:47 Migration 014_update_job_status_constraint.sql applied successfully +2026/02/25 06:54:47 Processing migration: 015_add_candidate_profile_fields.sql (from migrations/015_add_candidate_profile_fields.sql) +2026/02/25 06:54:47 Executing migration file migrations/015_add_candidate_profile_fields.sql +2026/02/25 06:54:47 Migration 015_add_candidate_profile_fields.sql applied successfully +2026/02/25 06:54:47 Processing migration: 016_create_notifications_table.sql (from migrations/016_create_notifications_table.sql) +2026/02/25 06:54:47 Executing migration file migrations/016_create_notifications_table.sql +2026/02/25 06:54:47 Warning while applying 016_create_notifications_table.sql: pq: relation "idx_notifications_user_id" already exists +2026/02/25 06:54:47 Processing migration: 017_create_tickets_table.sql (from migrations/017_create_tickets_table.sql) +2026/02/25 06:54:47 Executing migration file migrations/017_create_tickets_table.sql +2026/02/25 06:54:47 Warning while applying 017_create_tickets_table.sql: pq: relation "tickets" already exists +2026/02/25 06:54:47 Processing migration: 018_add_currency_to_jobs.sql (from migrations/018_add_currency_to_jobs.sql) +2026/02/25 06:54:47 Executing migration file migrations/018_add_currency_to_jobs.sql +2026/02/25 06:54:47 Migration 018_add_currency_to_jobs.sql applied successfully +2026/02/25 06:54:47 Processing migration: 019_create_job_payments_table.sql (from migrations/019_create_job_payments_table.sql) +2026/02/25 06:54:47 Executing migration file migrations/019_create_job_payments_table.sql +2026/02/25 06:54:47 Warning while applying 019_create_job_payments_table.sql: pq: relation "idx_job_payments_job_id" already exists +2026/02/25 06:54:47 Processing migration: 020_create_fcm_tokens_table.sql (from migrations/020_create_fcm_tokens_table.sql) +2026/02/25 06:54:47 Executing migration file migrations/020_create_fcm_tokens_table.sql +2026/02/25 06:54:47 Migration 020_create_fcm_tokens_table.sql applied successfully +2026/02/25 06:54:47 Processing migration: 021_location_hierarchy.sql (from migrations/021_location_hierarchy.sql) +2026/02/25 06:54:47 Executing migration file migrations/021_location_hierarchy.sql +2026/02/25 06:54:47 Warning while applying 021_location_hierarchy.sql: pq: relation "regions_old" already exists +2026/02/25 06:54:47 Processing migration: 022_fix_superadmin_role.sql (from migrations/022_fix_superadmin_role.sql) +2026/02/25 06:54:47 Executing migration file migrations/022_fix_superadmin_role.sql +2026/02/25 06:54:47 Migration 022_fix_superadmin_role.sql applied successfully +2026/02/25 06:54:47 Processing migration: 023_ensure_seeded_admins_roles.sql (from migrations/023_ensure_seeded_admins_roles.sql) +2026/02/25 06:54:47 Executing migration file migrations/023_ensure_seeded_admins_roles.sql +2026/02/25 06:54:47 Migration 023_ensure_seeded_admins_roles.sql applied successfully +2026/02/25 06:54:47 Processing migration: 024_create_external_services_credentials.sql (from migrations/024_create_external_services_credentials.sql) +2026/02/25 06:54:47 Executing migration file migrations/024_create_external_services_credentials.sql +2026/02/25 06:54:47 Warning while applying 024_create_external_services_credentials.sql: pq: relation "idx_service_name" already exists +2026/02/25 06:54:47 Processing migration: 025_create_chat_tables.sql (from migrations/025_create_chat_tables.sql) +2026/02/25 06:54:47 Executing migration file migrations/025_create_chat_tables.sql +2026/02/25 06:54:47 Warning while applying 025_create_chat_tables.sql: pq: relation "idx_conversations_candidate" already exists +2026/02/25 06:54:47 Processing migration: 026_create_system_settings.sql (from migrations/026_create_system_settings.sql) +2026/02/25 06:54:47 Executing migration file migrations/026_create_system_settings.sql +2026/02/25 06:54:47 Migration 026_create_system_settings.sql applied successfully +2026/02/25 06:54:47 Processing migration: 027_create_email_system.sql (from migrations/027_create_email_system.sql) +2026/02/25 06:54:47 Executing migration file migrations/027_create_email_system.sql +2026/02/25 06:54:47 Migration 027_create_email_system.sql applied successfully +2026/02/25 06:54:47 Processing migration: 028_add_avatar_url_to_users.sql (from migrations/028_add_avatar_url_to_users.sql) +2026/02/25 06:54:47 Executing migration file migrations/028_add_avatar_url_to_users.sql +2026/02/25 06:54:47 Migration 028_add_avatar_url_to_users.sql applied successfully +2026/02/25 06:54:47 Processing migration: 029_expand_employment_types.sql (from migrations/029_expand_employment_types.sql) +2026/02/25 06:54:47 Executing migration file migrations/029_expand_employment_types.sql +2026/02/25 06:54:47 Migration 029_expand_employment_types.sql applied successfully +2026/02/25 06:54:47 Processing migration: 030_add_salary_negotiable.sql (from migrations/030_add_salary_negotiable.sql) +2026/02/25 06:54:47 Executing migration file migrations/030_add_salary_negotiable.sql +2026/02/25 06:54:47 Migration 030_add_salary_negotiable.sql applied successfully +2026/02/25 06:54:47 Processing migration: 031_add_company_profile_fields.sql (from migrations/031_add_company_profile_fields.sql) +2026/02/25 06:54:47 Executing migration file migrations/031_add_company_profile_fields.sql +2026/02/25 06:54:47 Migration 031_add_company_profile_fields.sql applied successfully +2026/02/25 06:54:47 Processing migration: 032_update_superadmin_lol.sql (from migrations/032_update_superadmin_lol.sql) +2026/02/25 06:54:47 Executing migration file migrations/032_update_superadmin_lol.sql +2026/02/25 06:54:47 Migration 032_update_superadmin_lol.sql applied successfully +2026/02/25 06:54:47 Processing migration: 033_add_refactor_columns.sql (from migrations/033_add_refactor_columns.sql) +2026/02/25 06:54:47 Executing migration file migrations/033_add_refactor_columns.sql +2026/02/25 06:54:47 Migration 033_add_refactor_columns.sql applied successfully +2026/02/25 06:54:47 Processing migration: 034_create_video_interviews.sql (from migrations/034_create_video_interviews.sql) +2026/02/25 06:54:47 Executing migration file migrations/034_create_video_interviews.sql +2026/02/25 06:54:47 Migration 034_create_video_interviews.sql applied successfully +2026/02/25 06:54:47 Processing migration: 035_create_job_alerts.sql (from migrations/035_create_job_alerts.sql) +2026/02/25 06:54:47 Executing migration file migrations/035_create_job_alerts.sql +2026/02/25 06:54:47 Migration 035_create_job_alerts.sql applied successfully +2026/02/25 06:54:47 Processing migration: 036_create_company_followers.sql (from migrations/036_create_company_followers.sql) +2026/02/25 06:54:47 Executing migration file migrations/036_create_company_followers.sql +2026/02/25 06:54:47 Migration 036_create_company_followers.sql applied successfully +2026/02/25 06:54:47 Processing migration: 036_ensure_jobs_work_mode.sql (from migrations/036_ensure_jobs_work_mode.sql) +2026/02/25 06:54:47 Executing migration file migrations/036_ensure_jobs_work_mode.sql +2026/02/25 06:54:47 Migration 036_ensure_jobs_work_mode.sql applied successfully +2026/02/25 06:54:47 Processing migration: 037_add_date_posted_to_jobs.sql (from migrations/037_add_date_posted_to_jobs.sql) +2026/02/25 06:54:47 Executing migration file migrations/037_add_date_posted_to_jobs.sql +2026/02/25 06:54:47 Migration 037_add_date_posted_to_jobs.sql applied successfully +2026/02/25 06:54:47 Processing migration: 037_add_profile_fields_to_users.sql (from migrations/037_add_profile_fields_to_users.sql) +2026/02/25 06:54:47 Executing migration file migrations/037_add_profile_fields_to_users.sql +2026/02/25 06:54:47 Migration 037_add_profile_fields_to_users.sql applied successfully +2026/02/25 06:54:47 Processing migration: 038_create_password_reset_tokens.sql (from migrations/038_create_password_reset_tokens.sql) +2026/02/25 06:54:47 Executing migration file migrations/038_create_password_reset_tokens.sql +2026/02/25 06:54:47 Warning while applying 038_create_password_reset_tokens.sql: pq: relation "idx_reset_tokens_token" already exists +2026/02/25 06:54:47 Processing migration: 039_create_tickets_table_v2.sql (from migrations/039_create_tickets_table_v2.sql) +2026/02/25 06:54:47 Executing migration file migrations/039_create_tickets_table_v2.sql +2026/02/25 06:54:47 Migration 039_create_tickets_table_v2.sql applied successfully +2026/02/25 06:54:47 Processing migration: 040_create_activity_logs_table.sql (from migrations/040_create_activity_logs_table.sql) +2026/02/25 06:54:47 Executing migration file migrations/040_create_activity_logs_table.sql +2026/02/25 06:54:47 Migration 040_create_activity_logs_table.sql applied successfully +2026/02/25 06:54:47 Processing migration: 041_create_notifications_table_v2.sql (from migrations/041_create_notifications_table_v2.sql) +2026/02/25 06:54:47 Executing migration file migrations/041_create_notifications_table_v2.sql +2026/02/25 06:54:47 Migration 041_create_notifications_table_v2.sql applied successfully +2026/02/25 06:54:47 Processing migration: 042_add_view_count_and_job_views.sql (from migrations/042_add_view_count_and_job_views.sql) +2026/02/25 06:54:47 Executing migration file migrations/042_add_view_count_and_job_views.sql +2026/02/25 06:54:47 Migration 042_add_view_count_and_job_views.sql applied successfully +2026/02/25 06:54:47 Processing migration: 043_add_company_subscription.sql (from migrations/043_add_company_subscription.sql) +2026/02/25 06:54:47 Executing migration file migrations/043_add_company_subscription.sql +2026/02/25 06:54:47 Migration 043_add_company_subscription.sql applied successfully +2026/02/25 06:54:47 Processing migration: 044_add_category_to_tickets.sql (from migrations/044_add_category_to_tickets.sql) +2026/02/25 06:54:47 Executing migration file migrations/044_add_category_to_tickets.sql +2026/02/25 06:54:47 Migration 044_add_category_to_tickets.sql applied successfully +2026/02/25 06:54:47 Processing migration: 045_add_answers_to_applications.sql (from migrations/045_add_answers_to_applications.sql) +2026/02/25 06:54:47 Executing migration file migrations/045_add_answers_to_applications.sql +2026/02/25 06:54:47 Migration 045_add_answers_to_applications.sql applied successfully +2026/02/25 06:54:47 Processing migration: 046_fix_user_status_varchar.sql (from migrations/046_fix_user_status_varchar.sql) +2026/02/25 06:54:47 Executing migration file migrations/046_fix_user_status_varchar.sql +2026/02/25 06:54:47 Migration 046_fix_user_status_varchar.sql applied successfully +2026/02/25 06:54:47 Processing migration: 047_optimize_job_indexes.sql (from migrations/047_optimize_job_indexes.sql) +2026/02/25 06:54:47 Executing migration file migrations/047_optimize_job_indexes.sql +2026/02/25 06:54:47 Failed applying migration 047_optimize_job_indexes.sql: pq: function immutable_coalesce_dates(timestamp with time zone, timestamp without time zone) does not exist +exit status 1 diff --git a/backend/migrations/047_optimize_job_indexes.sql b/backend/migrations/047_optimize_job_indexes.sql new file mode 100644 index 0000000..ca2934c --- /dev/null +++ b/backend/migrations/047_optimize_job_indexes.sql @@ -0,0 +1,19 @@ +-- Migration: 047_optimize_job_indexes +-- Description: Creates an immutable wrapper function and a compound index to dramatically optimize the default sorting on the Homepage. +-- Removing the heavy Table Scan calculations for `ORDER BY j.is_featured DESC, COALESCE(j.date_posted, j.created_at) DESC`. + +-- 1. Create an immutable function wrapper for COALESCE because PostgreSQL forbids volatile timestamps in indexes +CREATE OR REPLACE FUNCTION immutable_coalesce_dates(d1 timestamp with time zone, d2 timestamp without time zone) +RETURNS timestamp with time zone AS $$ +BEGIN + RETURN COALESCE(d1, d2 AT TIME ZONE 'UTC'); +END; +$$ LANGUAGE plpgsql IMMUTABLE; + +-- 2. Create the precise index using the new immutable function +CREATE INDEX IF NOT EXISTS idx_jobs_featured_date_posted +ON jobs (is_featured DESC, immutable_coalesce_dates(date_posted, created_at) DESC); + +-- 3. Also index jobs creation date explicitly if they search strictly by recents without features +CREATE INDEX IF NOT EXISTS idx_jobs_coalesce_date_posted +ON jobs (immutable_coalesce_dates(date_posted, created_at) DESC);