package handlers import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "time" "github.com/DATA-DOG/go-sqlmock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestCRMHandler_ListCustomers(t *testing.T) { db, mock, err := sqlmock.New() require.NoError(t, err) defer db.Close() handler := NewCRMHandler(db) mock.ExpectQuery("SELECT id, name, email, phone, company, org_number, status, source, tags, assigned_to, created_at, updated_at FROM boc_customers"). WithArgs("active"). WillReturnRows(sqlmock.NewRows([]string{ "id", "name", "email", "phone", "company", "org_number", "status", "source", "tags", "assigned_to", "created_at", "updated_at", }).AddRow( "cust-1", "Test AB", "test@test.com", "+46701234567", "Test AB", "559141-7042", "active", "web", "{tag1,tag2}", nil, time.Now(), time.Now(), )) req := httptest.NewRequest(http.MethodGet, "/api/v1/crm/customers?status=active", nil) rr := httptest.NewRecorder() handler.ListCustomers(rr, req) assert.Equal(t, http.StatusOK, rr.Code) var response map[string]interface{} err = json.Unmarshal(rr.Body.Bytes(), &response) require.NoError(t, err) customers := response["customers"].([]interface{}) assert.Len(t, customers, 1) assert.Equal(t, float64(1), response["total"]) assert.NoError(t, mock.ExpectationsWereMet()) } func TestCRMHandler_CreateCustomer(t *testing.T) { db, mock, err := sqlmock.New() require.NoError(t, err) defer db.Close() handler := NewCRMHandler(db) mock.ExpectQuery("INSERT INTO boc_customers"). WithArgs("Test AB", "test@test.com", "+46701234567", "Test AB", "", "lead", "", nil). WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow("cust-1")) // NOTE: pq.Array([]string(nil)) becomes nil argument — sqlmock matches nil payload := Customer{ Name: "Test AB", Email: "test@test.com", Phone: "+46701234567", Company: "Test AB", Status: "lead", } body, _ := json.Marshal(payload) req := httptest.NewRequest(http.MethodPost, "/api/v1/crm/customers", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() handler.CreateCustomer(rr, req) assert.Equal(t, http.StatusCreated, rr.Code) var response map[string]interface{} err = json.Unmarshal(rr.Body.Bytes(), &response) require.NoError(t, err) assert.Equal(t, "cust-1", response["id"]) assert.NoError(t, mock.ExpectationsWereMet()) } func TestCRMHandler_GetCustomer(t *testing.T) { db, mock, err := sqlmock.New() require.NoError(t, err) defer db.Close() _ = NewCRMHandler(db) mock.ExpectQuery("SELECT id, name, email, phone, company, org_number, status, source, tags, assigned_to, created_at, updated_at"). WithArgs("cust-1"). WillReturnRows(sqlmock.NewRows([]string{ "id", "name", "email", "phone", "company", "org_number", "status", "source", "tags", "assigned_to", "created_at", "updated_at", }).AddRow( "cust-1", "Test AB", "test@test.com", "+46701234567", "Test AB", "559141-7042", "active", "web", nil, nil, time.Now(), time.Now(), )) // Requires chi router context for URL params — test via router in integration tests t.Skip("Requires chi router context for URL params") assert.NoError(t, mock.ExpectationsWereMet()) } func TestCRMHandler_GetPipeline(t *testing.T) { db, mock, err := sqlmock.New() require.NoError(t, err) defer db.Close() handler := NewCRMHandler(db) mock.ExpectQuery("SELECT stage, COUNT\\(\\*\\), COALESCE\\(SUM\\(value\\), 0\\)"). WillReturnRows(sqlmock.NewRows([]string{"stage", "count", "sum"}). AddRow("prospect", 5, 100000.00). AddRow("qualified", 3, 75000.00). AddRow("proposal", 2, 50000.00)) req := httptest.NewRequest(http.MethodGet, "/api/v1/crm/pipeline", nil) rr := httptest.NewRecorder() handler.GetPipeline(rr, req) assert.Equal(t, http.StatusOK, rr.Code) var response map[string]interface{} err = json.Unmarshal(rr.Body.Bytes(), &response) require.NoError(t, err) pipeline := response["pipeline"].([]interface{}) assert.Len(t, pipeline, 3) assert.NoError(t, mock.ExpectationsWereMet()) }