package handlers import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "time" "github.com/rede5/gohorsejobs/backend/internal/dto" "github.com/rede5/gohorsejobs/backend/internal/models" "github.com/stretchr/testify/assert" ) // mockApplicationService is a mock implementation for testing type mockApplicationService struct { createApplicationFunc func(req dto.CreateApplicationRequest) (*models.Application, error) getApplicationsFunc func(jobID int) ([]models.Application, error) getApplicationByIDFunc func(id int) (*models.Application, error) updateApplicationStatusFunc func(id int, req dto.UpdateApplicationStatusRequest) (*models.Application, error) } func (m *mockApplicationService) CreateApplication(req dto.CreateApplicationRequest) (*models.Application, error) { if m.createApplicationFunc != nil { return m.createApplicationFunc(req) } return nil, nil } func (m *mockApplicationService) GetApplications(jobID int) ([]models.Application, error) { if m.getApplicationsFunc != nil { return m.getApplicationsFunc(jobID) } return nil, nil } func (m *mockApplicationService) GetApplicationByID(id int) (*models.Application, error) { if m.getApplicationByIDFunc != nil { return m.getApplicationByIDFunc(id) } return nil, nil } func (m *mockApplicationService) UpdateApplicationStatus(id int, req dto.UpdateApplicationStatusRequest) (*models.Application, error) { if m.updateApplicationStatusFunc != nil { return m.updateApplicationStatusFunc(id, req) } return nil, nil } // ApplicationServiceInterface defines the interface for application service type ApplicationServiceInterface interface { CreateApplication(req dto.CreateApplicationRequest) (*models.Application, error) GetApplications(jobID int) ([]models.Application, error) GetApplicationByID(id int) (*models.Application, error) UpdateApplicationStatus(id int, req dto.UpdateApplicationStatusRequest) (*models.Application, error) } // testableApplicationHandler wraps an interface for testing type testableApplicationHandler struct { service ApplicationServiceInterface } func newTestableApplicationHandler(service ApplicationServiceInterface) *testableApplicationHandler { return &testableApplicationHandler{service: service} } func (h *testableApplicationHandler) CreateApplication(w http.ResponseWriter, r *http.Request) { var req dto.CreateApplicationRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } app, err := h.service.CreateApplication(req) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(app) } func (h *testableApplicationHandler) GetApplications(w http.ResponseWriter, r *http.Request, jobID int) { apps, err := h.service.GetApplications(jobID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(apps) } func (h *testableApplicationHandler) GetApplicationByID(w http.ResponseWriter, r *http.Request, id int) { app, err := h.service.GetApplicationByID(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(app) } func (h *testableApplicationHandler) UpdateApplicationStatus(w http.ResponseWriter, r *http.Request, id int) { var req dto.UpdateApplicationStatusRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } app, err := h.service.UpdateApplicationStatus(id, req) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(app) } // ============================================================================= // Test Cases // ============================================================================= func TestCreateApplication_Success(t *testing.T) { name := "John Doe" email := "john@example.com" mockService := &mockApplicationService{ createApplicationFunc: func(req dto.CreateApplicationRequest) (*models.Application, error) { return &models.Application{ ID: 1, JobID: req.JobID, Name: &name, Email: &email, Status: "pending", CreatedAt: time.Now(), UpdatedAt: time.Now(), }, nil }, } handler := newTestableApplicationHandler(mockService) appReq := dto.CreateApplicationRequest{ JobID: 1, Name: &name, Email: &email, } body, _ := json.Marshal(appReq) req := httptest.NewRequest("POST", "/applications", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() handler.CreateApplication(rr, req) assert.Equal(t, http.StatusCreated, rr.Code) var app models.Application err := json.Unmarshal(rr.Body.Bytes(), &app) assert.NoError(t, err) assert.Equal(t, "pending", app.Status) assert.Equal(t, 1, app.JobID) } func TestCreateApplication_InvalidJSON(t *testing.T) { mockService := &mockApplicationService{} handler := newTestableApplicationHandler(mockService) req := httptest.NewRequest("POST", "/applications", bytes.NewReader([]byte("invalid"))) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() handler.CreateApplication(rr, req) assert.Equal(t, http.StatusBadRequest, rr.Code) } func TestCreateApplication_ServiceError(t *testing.T) { mockService := &mockApplicationService{ createApplicationFunc: func(req dto.CreateApplicationRequest) (*models.Application, error) { return nil, assert.AnError }, } handler := newTestableApplicationHandler(mockService) appReq := dto.CreateApplicationRequest{JobID: 1} body, _ := json.Marshal(appReq) req := httptest.NewRequest("POST", "/applications", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() handler.CreateApplication(rr, req) assert.Equal(t, http.StatusInternalServerError, rr.Code) } func TestGetApplications_Success(t *testing.T) { name1 := "John Doe" name2 := "Jane Smith" mockService := &mockApplicationService{ getApplicationsFunc: func(jobID int) ([]models.Application, error) { return []models.Application{ { ID: 1, JobID: jobID, Name: &name1, Status: "pending", CreatedAt: time.Now(), }, { ID: 2, JobID: jobID, Name: &name2, Status: "reviewed", CreatedAt: time.Now(), }, }, nil }, } handler := newTestableApplicationHandler(mockService) req := httptest.NewRequest("GET", "/applications?jobId=1", nil) rr := httptest.NewRecorder() handler.GetApplications(rr, req, 1) assert.Equal(t, http.StatusOK, rr.Code) var apps []models.Application err := json.Unmarshal(rr.Body.Bytes(), &apps) assert.NoError(t, err) assert.Len(t, apps, 2) } func TestGetApplications_Empty(t *testing.T) { mockService := &mockApplicationService{ getApplicationsFunc: func(jobID int) ([]models.Application, error) { return []models.Application{}, nil }, } handler := newTestableApplicationHandler(mockService) req := httptest.NewRequest("GET", "/applications?jobId=1", nil) rr := httptest.NewRecorder() handler.GetApplications(rr, req, 1) assert.Equal(t, http.StatusOK, rr.Code) } func TestGetApplications_Error(t *testing.T) { mockService := &mockApplicationService{ getApplicationsFunc: func(jobID int) ([]models.Application, error) { return nil, assert.AnError }, } handler := newTestableApplicationHandler(mockService) req := httptest.NewRequest("GET", "/applications?jobId=1", nil) rr := httptest.NewRecorder() handler.GetApplications(rr, req, 1) assert.Equal(t, http.StatusInternalServerError, rr.Code) } func TestGetApplicationByID_Success(t *testing.T) { name := "John Doe" mockService := &mockApplicationService{ getApplicationByIDFunc: func(id int) (*models.Application, error) { return &models.Application{ ID: id, JobID: 1, Name: &name, Status: "pending", CreatedAt: time.Now(), }, nil }, } handler := newTestableApplicationHandler(mockService) req := httptest.NewRequest("GET", "/applications/1", nil) rr := httptest.NewRecorder() handler.GetApplicationByID(rr, req, 1) assert.Equal(t, http.StatusOK, rr.Code) var app models.Application err := json.Unmarshal(rr.Body.Bytes(), &app) assert.NoError(t, err) assert.Equal(t, 1, app.ID) } func TestGetApplicationByID_NotFound(t *testing.T) { mockService := &mockApplicationService{ getApplicationByIDFunc: func(id int) (*models.Application, error) { return nil, assert.AnError }, } handler := newTestableApplicationHandler(mockService) req := httptest.NewRequest("GET", "/applications/999", nil) rr := httptest.NewRecorder() handler.GetApplicationByID(rr, req, 999) assert.Equal(t, http.StatusNotFound, rr.Code) } func TestUpdateApplicationStatus_Success(t *testing.T) { name := "John Doe" mockService := &mockApplicationService{ updateApplicationStatusFunc: func(id int, req dto.UpdateApplicationStatusRequest) (*models.Application, error) { return &models.Application{ ID: id, JobID: 1, Name: &name, Status: req.Status, CreatedAt: time.Now(), UpdatedAt: time.Now(), }, nil }, } handler := newTestableApplicationHandler(mockService) statusReq := dto.UpdateApplicationStatusRequest{ Status: "hired", } body, _ := json.Marshal(statusReq) req := httptest.NewRequest("PUT", "/applications/1/status", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() handler.UpdateApplicationStatus(rr, req, 1) assert.Equal(t, http.StatusOK, rr.Code) var app models.Application err := json.Unmarshal(rr.Body.Bytes(), &app) assert.NoError(t, err) assert.Equal(t, "hired", app.Status) } func TestUpdateApplicationStatus_InvalidJSON(t *testing.T) { mockService := &mockApplicationService{} handler := newTestableApplicationHandler(mockService) req := httptest.NewRequest("PUT", "/applications/1/status", bytes.NewReader([]byte("invalid"))) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() handler.UpdateApplicationStatus(rr, req, 1) assert.Equal(t, http.StatusBadRequest, rr.Code) } func TestUpdateApplicationStatus_Error(t *testing.T) { mockService := &mockApplicationService{ updateApplicationStatusFunc: func(id int, req dto.UpdateApplicationStatusRequest) (*models.Application, error) { return nil, assert.AnError }, } handler := newTestableApplicationHandler(mockService) statusReq := dto.UpdateApplicationStatusRequest{Status: "hired"} body, _ := json.Marshal(statusReq) req := httptest.NewRequest("PUT", "/applications/1/status", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() handler.UpdateApplicationStatus(rr, req, 1) assert.Equal(t, http.StatusInternalServerError, rr.Code) }