241 lines
6.2 KiB
Go
241 lines
6.2 KiB
Go
|
|
package handlers
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"encoding/json"
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"github.com/go-chi/chi/v5"
|
||
|
|
"github.com/stretchr/testify/assert"
|
||
|
|
"github.com/stretchr/testify/require"
|
||
|
|
)
|
||
|
|
|
||
|
|
// MockDB is a simple mock for testing
|
||
|
|
type MockDB struct{}
|
||
|
|
|
||
|
|
func TestHealthHandler(t *testing.T) {
|
||
|
|
handler := NewHealthHandler()
|
||
|
|
req := httptest.NewRequest(http.MethodGet, "/health", nil)
|
||
|
|
rr := httptest.NewRecorder()
|
||
|
|
|
||
|
|
handler.ServeHTTP(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)
|
||
|
|
assert.Equal(t, true, response["ok"])
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWriteJSON(t *testing.T) {
|
||
|
|
rr := httptest.NewRecorder()
|
||
|
|
data := map[string]string{"key": "value"}
|
||
|
|
|
||
|
|
writeJSON(rr, http.StatusOK, data)
|
||
|
|
|
||
|
|
assert.Equal(t, http.StatusOK, rr.Code)
|
||
|
|
assert.Equal(t, "application/json", rr.Header().Get("Content-Type"))
|
||
|
|
|
||
|
|
var response map[string]string
|
||
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
||
|
|
require.NoError(t, err)
|
||
|
|
assert.Equal(t, "value", response["key"])
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWriteError(t *testing.T) {
|
||
|
|
rr := httptest.NewRecorder()
|
||
|
|
writeError(rr, http.StatusBadRequest, "test error")
|
||
|
|
|
||
|
|
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||
|
|
|
||
|
|
var response map[string]interface{}
|
||
|
|
err := json.Unmarshal(rr.Body.Bytes(), &response)
|
||
|
|
require.NoError(t, err)
|
||
|
|
assert.Equal(t, "test error", response["error"])
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCRMHandler_CreateCustomer(t *testing.T) {
|
||
|
|
// This would need a real or mocked DB connection
|
||
|
|
// For now, just test the request parsing
|
||
|
|
|
||
|
|
payload := map[string]interface{}{
|
||
|
|
"name": "Test Customer",
|
||
|
|
"email": "test@example.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")
|
||
|
|
|
||
|
|
// Without DB, this will fail, but we test the request structure
|
||
|
|
assert.NotNil(t, req)
|
||
|
|
assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestQuoteHandler_CreateQuote(t *testing.T) {
|
||
|
|
payload := map[string]interface{}{
|
||
|
|
"customer_id": "test-customer-id",
|
||
|
|
"title": "Test Quote",
|
||
|
|
"description": "Test description",
|
||
|
|
"valid_until": "2026-12-31",
|
||
|
|
"items": []map[string]interface{}{
|
||
|
|
{
|
||
|
|
"description": "Item 1",
|
||
|
|
"quantity": 2,
|
||
|
|
"unit_price": 100.00,
|
||
|
|
"tax_rate": 25.0,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
body, _ := json.Marshal(payload)
|
||
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/sales/quotes", bytes.NewReader(body))
|
||
|
|
req.Header.Set("Content-Type", "application/json")
|
||
|
|
|
||
|
|
assert.NotNil(t, req)
|
||
|
|
|
||
|
|
var parsed map[string]interface{}
|
||
|
|
err := json.Unmarshal(body, &parsed)
|
||
|
|
require.NoError(t, err)
|
||
|
|
assert.Equal(t, "Test Quote", parsed["title"])
|
||
|
|
|
||
|
|
items := parsed["items"].([]interface{})
|
||
|
|
assert.Len(t, items, 1)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestSubscriptionHandler_CreateSubscription(t *testing.T) {
|
||
|
|
payload := map[string]interface{}{
|
||
|
|
"customer_id": "test-customer",
|
||
|
|
"plan_id": "test-plan",
|
||
|
|
"start_date": "2026-07-12",
|
||
|
|
}
|
||
|
|
|
||
|
|
body, _ := json.Marshal(payload)
|
||
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/subscriptions", bytes.NewReader(body))
|
||
|
|
req.Header.Set("Content-Type", "application/json")
|
||
|
|
|
||
|
|
assert.NotNil(t, req)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBankHandler_MatchTransaction(t *testing.T) {
|
||
|
|
payload := map[string]interface{}{
|
||
|
|
"match_type": "invoice",
|
||
|
|
"match_id": "inv-123",
|
||
|
|
}
|
||
|
|
|
||
|
|
body, _ := json.Marshal(payload)
|
||
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/bank/transactions/tx-123/match", bytes.NewReader(body))
|
||
|
|
req.Header.Set("Content-Type", "application/json")
|
||
|
|
|
||
|
|
assert.NotNil(t, req)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProjectHandler_AddTime(t *testing.T) {
|
||
|
|
payload := map[string]interface{}{
|
||
|
|
"employee_id": "emp-1",
|
||
|
|
"date": "2026-07-12",
|
||
|
|
"hours": 8.0,
|
||
|
|
"description": "Development work",
|
||
|
|
"billable": true,
|
||
|
|
"hourly_rate": 150.00,
|
||
|
|
}
|
||
|
|
|
||
|
|
body, _ := json.Marshal(payload)
|
||
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/projects/proj-1/time", bytes.NewReader(body))
|
||
|
|
req.Header.Set("Content-Type", "application/json")
|
||
|
|
|
||
|
|
assert.NotNil(t, req)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestReceiptHandler_UploadReceipt(t *testing.T) {
|
||
|
|
payload := map[string]interface{}{
|
||
|
|
"employee_id": "emp-1",
|
||
|
|
"image_url": "https://example.com/receipt.jpg",
|
||
|
|
}
|
||
|
|
|
||
|
|
body, _ := json.Marshal(payload)
|
||
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/receipts", bytes.NewReader(body))
|
||
|
|
req.Header.Set("Content-Type", "application/json")
|
||
|
|
|
||
|
|
assert.NotNil(t, req)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestPayrollHandler_ProcessPayroll(t *testing.T) {
|
||
|
|
// Test that the endpoint exists and accepts POST
|
||
|
|
router := chi.NewRouter()
|
||
|
|
router.Post("/api/v1/payroll/runs/{id}/process", func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Payroll processed"})
|
||
|
|
})
|
||
|
|
|
||
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/payroll/runs/run-1/process", nil)
|
||
|
|
rr := httptest.NewRecorder()
|
||
|
|
|
||
|
|
router.ServeHTTP(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)
|
||
|
|
assert.Equal(t, "Payroll processed", response["message"])
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestInventoryHandler_AdjustStock(t *testing.T) {
|
||
|
|
payload := map[string]interface{}{
|
||
|
|
"product_id": "prod-1",
|
||
|
|
"warehouse_id": "wh-1",
|
||
|
|
"quantity": 100.0,
|
||
|
|
"reason": "Initial stock",
|
||
|
|
}
|
||
|
|
|
||
|
|
body, _ := json.Marshal(payload)
|
||
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/inventory/adjust", bytes.NewReader(body))
|
||
|
|
req.Header.Set("Content-Type", "application/json")
|
||
|
|
|
||
|
|
assert.NotNil(t, req)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Benchmark tests
|
||
|
|
func BenchmarkWriteJSON(b *testing.B) {
|
||
|
|
data := map[string]interface{}{
|
||
|
|
"id": "test-id",
|
||
|
|
"name": "Test",
|
||
|
|
"amount": 1000.00,
|
||
|
|
"items": []string{"a", "b", "c"},
|
||
|
|
}
|
||
|
|
|
||
|
|
for i := 0; i < b.N; i++ {
|
||
|
|
rr := httptest.NewRecorder()
|
||
|
|
writeJSON(rr, http.StatusOK, data)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func BenchmarkQuoteCalculation(b *testing.B) {
|
||
|
|
items := []struct {
|
||
|
|
Quantity float64
|
||
|
|
UnitPrice float64
|
||
|
|
TaxRate float64
|
||
|
|
Discount float64
|
||
|
|
}{
|
||
|
|
{2, 100, 25, 0},
|
||
|
|
{5, 50, 25, 10},
|
||
|
|
{1, 200, 25, 0},
|
||
|
|
}
|
||
|
|
|
||
|
|
for i := 0; i < b.N; i++ {
|
||
|
|
var total float64
|
||
|
|
for _, item := range items {
|
||
|
|
itemTotal := item.Quantity * item.UnitPrice * (1 - item.Discount/100)
|
||
|
|
itemTax := itemTotal * (item.TaxRate / 100)
|
||
|
|
total += itemTotal + itemTax
|
||
|
|
}
|
||
|
|
_ = total
|
||
|
|
}
|
||
|
|
}
|