package handlers import ( "bytes" "database/sql" "encoding/json" "net/http" "net/http/httptest" "testing" "time" "github.com/DATA-DOG/go-sqlmock" "github.com/go-chi/chi/v5" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func setupCRMHandler(t *testing.T) (*CRMHandler, sqlmock.Sqlmock, *sql.DB) { db, mock, err := sqlmock.New() require.NoError(t, err) handler := NewCRMHandler(db) return handler, mock, db } func TestCRMHandler_CreateCustomer(t *testing.T) { handler, mock, db := setupCRMHandler(t) defer db.Close() // Expect INSERT — matches actual handler SQL mock.ExpectQuery("INSERT INTO boc_customers"). WithArgs("Test Corp", "contact@test.com", "+1234567890", "", "", "lead", "", sqlmock.AnyArg()). WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(uuid.New().String())) body := map[string]interface{}{ "name": "Test Corp", "email": "contact@test.com", "phone": "+1234567890", "status": "lead", } jsonBody, _ := json.Marshal(body) req := httptest.NewRequest(http.MethodPost, "/api/customers", bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() handler.CreateCustomer(rr, req) assert.Equal(t, http.StatusCreated, rr.Code) assert.NoError(t, mock.ExpectationsWereMet()) } func TestCRMHandler_GetCustomer(t *testing.T) { handler, mock, db := setupCRMHandler(t) defer db.Close() customerID := uuid.New().String() // Expect SELECT — matches actual handler SQL with pq.StringArray mock.ExpectQuery("SELECT (.+) FROM boc_customers WHERE id = \\$(.+)"). WithArgs(customerID). WillReturnRows(sqlmock.NewRows([]string{ "id", "name", "email", "phone", "company", "org_number", "status", "source", "tags", "assigned_to", "created_at", "updated_at", }).AddRow( customerID, "Test Corp", "test@test.com", "+1234567890", "", "", "active", "", "{tag1,tag2}", nil, time.Now(), time.Now(), )) // Use chi router to set URL param r := chi.NewRouter() r.Get("/api/customers/{id}", handler.GetCustomer) req := httptest.NewRequest(http.MethodGet, "/api/customers/"+customerID, nil) rr := httptest.NewRecorder() r.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) assert.NoError(t, mock.ExpectationsWereMet()) } func TestCRMHandler_GetCustomer_NotFound(t *testing.T) { handler, mock, db := setupCRMHandler(t) defer db.Close() customerID := uuid.New().String() mock.ExpectQuery("SELECT (.+) FROM boc_customers WHERE id = \\$(.+)"). WithArgs(customerID). WillReturnError(sql.ErrNoRows) r := chi.NewRouter() r.Get("/api/customers/{id}", handler.GetCustomer) req := httptest.NewRequest(http.MethodGet, "/api/customers/"+customerID, nil) rr := httptest.NewRecorder() r.ServeHTTP(rr, req) assert.Equal(t, http.StatusNotFound, rr.Code) assert.NoError(t, mock.ExpectationsWereMet()) } func TestCRMHandler_UpdateCustomer(t *testing.T) { handler, mock, db := setupCRMHandler(t) defer db.Close() customerID := uuid.New().String() // Expect UPDATE — matches actual handler SQL (10 args) // Use AnyArg for pq.Array since nil slice encoding varies mock.ExpectExec("UPDATE boc_customers SET"). WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()). WillReturnResult(sqlmock.NewResult(1, 1)) body := map[string]interface{}{ "name": "Updated Corp", "email": "updated@test.com", } jsonBody, _ := json.Marshal(body) // Use chi router r := chi.NewRouter() r.Put("/api/customers/{id}", handler.UpdateCustomer) req := httptest.NewRequest(http.MethodPut, "/api/customers/"+customerID, bytes.NewReader(jsonBody)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() r.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) assert.NoError(t, mock.ExpectationsWereMet()) } func TestCRMHandler_DeleteCustomer(t *testing.T) { handler, mock, db := setupCRMHandler(t) defer db.Close() customerID := uuid.New().String() // Expect DELETE mock.ExpectExec("DELETE FROM boc_customers WHERE id = \\$(.+)"). WithArgs(customerID). WillReturnResult(sqlmock.NewResult(1, 1)) // Use chi router r := chi.NewRouter() r.Delete("/api/customers/{id}", handler.DeleteCustomer) req := httptest.NewRequest(http.MethodDelete, "/api/customers/"+customerID, nil) rr := httptest.NewRecorder() r.ServeHTTP(rr, req) assert.Equal(t, http.StatusOK, rr.Code) assert.NoError(t, mock.ExpectationsWereMet()) }