package handlers import ( "bytes" "context" "encoding/json" "errors" "net/http" "net/http/httptest" "testing" "github.com/rede5/gohorsejobs/backend/internal/api/middleware" "github.com/rede5/gohorsejobs/backend/internal/dto" "github.com/rede5/gohorsejobs/backend/internal/models" "github.com/stretchr/testify/assert" ) // mockJobService is a mock implementation of the job service for testing type mockJobService struct { getJobsFunc func(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error) createJobFunc func(req dto.CreateJobRequest, createdBy string) (*models.Job, error) getJobByIDFunc func(id string) (*models.Job, error) updateJobFunc func(id string, req dto.UpdateJobRequest) (*models.Job, error) deleteJobFunc func(id string) error } func (m *mockJobService) GetJobs(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error) { if m.getJobsFunc != nil { return m.getJobsFunc(filter) } return nil, 0, nil } func (m *mockJobService) CreateJob(req dto.CreateJobRequest, createdBy string) (*models.Job, error) { if m.createJobFunc != nil { return m.createJobFunc(req, createdBy) } return nil, nil } func (m *mockJobService) GetJobByID(id string) (*models.Job, error) { if m.getJobByIDFunc != nil { return m.getJobByIDFunc(id) } return nil, nil } func (m *mockJobService) UpdateJob(id string, req dto.UpdateJobRequest) (*models.Job, error) { if m.updateJobFunc != nil { return m.updateJobFunc(id, req) } return nil, nil } func (m *mockJobService) DeleteJob(id string) error { if m.deleteJobFunc != nil { return m.deleteJobFunc(id) } return nil } func TestGetJobs_Success(t *testing.T) { mockService := &mockJobService{ getJobsFunc: func(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error) { return []models.JobWithCompany{ { Job: models.Job{ID: "1", Title: "Software Engineer", Status: "open"}, CompanyName: "TestCorp", }, { Job: models.Job{ID: "2", Title: "DevOps", Status: "open"}, CompanyName: "TestCorp", }, }, 2, nil }, } handler := NewJobHandler(mockService) req := httptest.NewRequest("GET", "/jobs", nil) rr := httptest.NewRecorder() handler.GetJobs(rr, req) assert.Equal(t, http.StatusOK, rr.Code) var response dto.PaginatedResponse err := json.Unmarshal(rr.Body.Bytes(), &response) assert.NoError(t, err) assert.Equal(t, 2, response.Pagination.Total) } func TestGetJobs_ParsesCareerjetAliases(t *testing.T) { mockService := &mockJobService{ getJobsFunc: func(filter dto.JobFilterQuery) ([]models.JobWithCompany, int, error) { if assert.NotNil(t, filter.Search) { assert.Equal(t, "software engineer", *filter.Search) } if assert.NotNil(t, filter.Location) { assert.Equal(t, "Sao Paulo", *filter.Location) } if assert.NotNil(t, filter.LocationSearch) { assert.Equal(t, "Sao Paulo", *filter.LocationSearch) } return []models.JobWithCompany{}, 0, nil }, } handler := NewJobHandler(mockService) req := httptest.NewRequest("GET", "/jobs?s=software+engineer&l=Sao+Paulo", nil) rr := httptest.NewRecorder() handler.GetJobs(rr, req) assert.Equal(t, http.StatusOK, rr.Code) } func TestCreateJob_Success(t *testing.T) { mockService := &mockJobService{ createJobFunc: func(req dto.CreateJobRequest, createdBy string) (*models.Job, error) { assert.Equal(t, "user-123", createdBy) if assert.NotNil(t, req.WorkMode) { assert.Equal(t, "remote", *req.WorkMode) } return &models.Job{ ID: "1", Title: req.Title, Status: "open", WorkMode: req.WorkMode, }, nil }, } handler := NewJobHandler(mockService) jobReq := dto.CreateJobRequest{ CompanyID: "1", Title: "Backend Developer", Description: "Build awesome APIs for the platform", Status: "open", WorkMode: func() *string { s := "remote"; return &s }(), } body, _ := json.Marshal(jobReq) req := httptest.NewRequest("POST", "/jobs", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") // Inject Context ctx := context.WithValue(req.Context(), middleware.ContextUserID, "user-123") req = req.WithContext(ctx) rr := httptest.NewRecorder() handler.CreateJob(rr, req) assert.Equal(t, http.StatusCreated, rr.Code) var job models.Job err := json.Unmarshal(rr.Body.Bytes(), &job) assert.NoError(t, err) assert.Equal(t, "Backend Developer", job.Title) if assert.NotNil(t, job.WorkMode) { assert.Equal(t, "remote", *job.WorkMode) } } func TestCreateJob_ServiceError(t *testing.T) { mockService := &mockJobService{ createJobFunc: func(req dto.CreateJobRequest, createdBy string) (*models.Job, error) { return nil, errors.New("db error") }, } handler := NewJobHandler(mockService) jobReq := dto.CreateJobRequest{ CompanyID: "company-1", Title: "Failing Job Title", Description: "This description is long enough to pass validation", Status: "open", } body, _ := json.Marshal(jobReq) req := httptest.NewRequest("POST", "/jobs", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") ctx := context.WithValue(req.Context(), middleware.ContextUserID, "user-123") req = req.WithContext(ctx) rr := httptest.NewRecorder() handler.CreateJob(rr, req) assert.Equal(t, http.StatusInternalServerError, rr.Code) } func TestGetJobByID_Success(t *testing.T) { mockService := &mockJobService{ getJobByIDFunc: func(id string) (*models.Job, error) { return &models.Job{ID: id, Title: "Software Engineer"}, nil }, } handler := NewJobHandler(mockService) req := httptest.NewRequest("GET", "/jobs/1", nil) req.SetPathValue("id", "1") rr := httptest.NewRecorder() handler.GetJobByID(rr, req) assert.Equal(t, http.StatusOK, rr.Code) } func TestDeleteJob_Success(t *testing.T) { mockService := &mockJobService{ deleteJobFunc: func(id string) error { return nil }, } handler := NewJobHandler(mockService) req := httptest.NewRequest("DELETE", "/jobs/1", nil) req.SetPathValue("id", "1") rr := httptest.NewRecorder() handler.DeleteJob(rr, req) assert.Equal(t, http.StatusNoContent, rr.Code) } func TestUpdateJob_Success(t *testing.T) { workMode := "hybrid" mockService := &mockJobService{ updateJobFunc: func(id string, req dto.UpdateJobRequest) (*models.Job, error) { if assert.NotNil(t, req.WorkMode) { assert.Equal(t, workMode, *req.WorkMode) } return &models.Job{ID: id, Title: "Updated"}, nil }, } handler := NewJobHandler(mockService) reqBody, _ := json.Marshal(dto.UpdateJobRequest{Title: func() *string { s := "Updated"; return &s }()}) // Inline pointer helper not clean but works or define var // Cleaner title := "Updated" reqBody, _ = json.Marshal(dto.UpdateJobRequest{Title: &title, WorkMode: &workMode}) req := httptest.NewRequest("PUT", "/jobs/1", bytes.NewReader(reqBody)) req.SetPathValue("id", "1") rr := httptest.NewRecorder() handler.UpdateJob(rr, req) assert.Equal(t, http.StatusOK, rr.Code) }