package services_test import ( "regexp" "testing" "time" "github.com/DATA-DOG/go-sqlmock" "github.com/rede5/gohorsejobs/backend/internal/dto" "github.com/rede5/gohorsejobs/backend/internal/services" "github.com/stretchr/testify/assert" ) func TestCreateJob(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() service := services.NewJobService(db) tests := []struct { name string req dto.CreateJobRequest mockRun func() wantErr bool }{ { name: "Success", req: dto.CreateJobRequest{ CompanyID: "1", Title: "Go Developer", Status: "published", }, 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())) }, wantErr: false, }, { name: "DB Error", req: dto.CreateJobRequest{ CompanyID: "1", Title: "Go Developer", }, mockRun: func() { mock.ExpectQuery(regexp.QuoteMeta(`INSERT INTO jobs`)). WillReturnError(assert.AnError) }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.mockRun() got, err := service.CreateJob(tt.req, "user-123") if (err != nil) != tt.wantErr { t.Errorf("JobService.CreateJob() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr { assert.Equal(t, "100", got.ID) } }) } } func TestGetJobs(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() service := services.NewJobService(db) tests := []struct { name string filter dto.JobFilterQuery mockRun func() wantErr bool }{ { name: "List All", filter: dto.JobFilterQuery{ PaginationQuery: dto.PaginationQuery{Page: 1, Limit: 10}, }, mockRun: func() { // 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`)). 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", }).AddRow( "1", "10", "Dev", "Desc", 100, 200, "m", "ft", "Remote", "40h", "Remote", "open", true, false, time.Now(), time.Now(), "Acme", "url", "Region", "City", )) // Count query mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM jobs j WHERE 1=1`)). WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(2)) }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.mockRun() _, _, err := service.GetJobs(tt.filter) if (err != nil) != tt.wantErr { t.Errorf("JobService.GetJobs() error = %v, wantErr %v", err, tt.wantErr) return } }) } } func TestGetJobByID(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() service := services.NewJobService(db) 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", }).AddRow( jobID, 1, "Title", "Desc", 100, 200, "m", "ft", "40h", "Remote", 0, 0, false, nil, nil, false, "N2", "open", time.Now(), time.Now(), ) mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, company_id`)). WithArgs(jobID). WillReturnRows(rows) job, err := service.GetJobByID(jobID) if err != nil { t.Errorf("GetJobByID() error = %v", err) return } if job == nil || job.ID != jobID { t.Errorf("GetJobByID() got = %v, want ID %s", job, jobID) } } func TestUpdateJob(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() service := services.NewJobService(db) jobID := "100" newTitle := "New Title" req := dto.UpdateJobRequest{ Title: &newTitle, } // Expect UPDATE with RETURNING mock.ExpectQuery(regexp.QuoteMeta(`UPDATE jobs SET title = $1, updated_at = NOW() WHERE id = $2 RETURNING id, updated_at`)). 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", }).AddRow( jobID, 1, newTitle, "Desc", 100, 200, "m", "ft", "40h", "Remote", 0, 0, false, nil, nil, false, "N2", "open", time.Now(), time.Now(), ) mock.ExpectQuery(regexp.QuoteMeta(`SELECT id, company_id`)). WithArgs(jobID). WillReturnRows(rows) job, err := service.UpdateJob(jobID, req) if err != nil { t.Errorf("UpdateJob() error = %v", err) return } if job.Title != newTitle { t.Errorf("UpdateJob() title = %s, want %s", job.Title, newTitle) } } func TestDeleteJob(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() service := services.NewJobService(db) jobID := "100" mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM jobs WHERE id = $1`)). WithArgs(jobID). WillReturnResult(sqlmock.NewResult(1, 1)) err = service.DeleteJob(jobID) if err != nil { t.Errorf("DeleteJob() error = %v", err) } }