304 lines
7.2 KiB
Go
304 lines
7.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/csv"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.mchus.pro/mchus/quoteforge/internal/config"
|
|
"git.mchus.pro/mchus/quoteforge/internal/models"
|
|
"git.mchus.pro/mchus/quoteforge/internal/services"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// Mock services for testing
|
|
type mockConfigService struct {
|
|
config *models.Configuration
|
|
err error
|
|
}
|
|
|
|
func (m *mockConfigService) GetByUUID(uuid string, ownerUsername string) (*models.Configuration, error) {
|
|
return m.config, m.err
|
|
}
|
|
|
|
func TestExportCSV_Success(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
// Create handler with mocks
|
|
exportSvc := services.NewExportService(config.ExportConfig{}, nil, nil)
|
|
handler := NewExportHandler(
|
|
exportSvc,
|
|
&mockConfigService{},
|
|
nil,
|
|
"testuser",
|
|
)
|
|
|
|
// Create JSON request body
|
|
jsonBody := `{
|
|
"name": "Test Export",
|
|
"items": [
|
|
{
|
|
"lot_name": "LOT-001",
|
|
"quantity": 2,
|
|
"unit_price": 100.50
|
|
}
|
|
],
|
|
"notes": "Test notes"
|
|
}`
|
|
|
|
// Create HTTP request
|
|
req, _ := http.NewRequest("POST", "/api/export/csv", bytes.NewBufferString(jsonBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Create response recorder
|
|
w := httptest.NewRecorder()
|
|
|
|
// Create Gin context
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
|
|
// Call handler
|
|
handler.ExportCSV(c)
|
|
|
|
// Check status code
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("Expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
// Check Content-Type header
|
|
contentType := w.Header().Get("Content-Type")
|
|
if contentType != "text/csv; charset=utf-8" {
|
|
t.Errorf("Expected Content-Type 'text/csv; charset=utf-8', got %q", contentType)
|
|
}
|
|
|
|
// Check for BOM
|
|
responseBody := w.Body.Bytes()
|
|
if len(responseBody) < 3 {
|
|
t.Fatalf("Response too short to contain BOM")
|
|
}
|
|
|
|
expectedBOM := []byte{0xEF, 0xBB, 0xBF}
|
|
actualBOM := responseBody[:3]
|
|
if !bytes.Equal(actualBOM, expectedBOM) {
|
|
t.Errorf("UTF-8 BOM mismatch. Expected %v, got %v", expectedBOM, actualBOM)
|
|
}
|
|
|
|
// Check semicolon delimiter in CSV
|
|
reader := csv.NewReader(bytes.NewReader(responseBody[3:]))
|
|
reader.Comma = ';'
|
|
|
|
header, err := reader.Read()
|
|
if err != nil {
|
|
t.Errorf("Failed to parse CSV header: %v", err)
|
|
}
|
|
|
|
if len(header) != 8 {
|
|
t.Errorf("Expected 8 columns, got %d", len(header))
|
|
}
|
|
}
|
|
|
|
func TestExportCSV_InvalidRequest(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
exportSvc := services.NewExportService(config.ExportConfig{}, nil, nil)
|
|
handler := NewExportHandler(
|
|
exportSvc,
|
|
&mockConfigService{},
|
|
nil,
|
|
"testuser",
|
|
)
|
|
|
|
// Create invalid request (missing required field)
|
|
req, _ := http.NewRequest("POST", "/api/export/csv", bytes.NewBufferString(`{"name": "Test"}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
|
|
handler.ExportCSV(c)
|
|
|
|
// Should return 400 Bad Request
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("Expected status 400, got %d", w.Code)
|
|
}
|
|
|
|
// Should return JSON error
|
|
var errResp map[string]interface{}
|
|
json.Unmarshal(w.Body.Bytes(), &errResp)
|
|
if _, hasError := errResp["error"]; !hasError {
|
|
t.Errorf("Expected error in JSON response")
|
|
}
|
|
}
|
|
|
|
func TestExportCSV_EmptyItems(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
exportSvc := services.NewExportService(config.ExportConfig{}, nil, nil)
|
|
handler := NewExportHandler(
|
|
exportSvc,
|
|
&mockConfigService{},
|
|
nil,
|
|
"testuser",
|
|
)
|
|
|
|
// Create request with empty items array - should fail binding validation
|
|
req, _ := http.NewRequest("POST", "/api/export/csv", bytes.NewBufferString(`{"name":"Empty Export","items":[],"notes":""}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
|
|
handler.ExportCSV(c)
|
|
|
|
// Should return 400 Bad Request (validation error from gin binding)
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Logf("Status code: %d (expected 400 for empty items)", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestExportConfigCSV_Success(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
// Mock configuration
|
|
mockConfig := &models.Configuration{
|
|
UUID: "test-uuid",
|
|
Name: "Test Config",
|
|
OwnerUsername: "testuser",
|
|
Items: models.ConfigItems{
|
|
{
|
|
LotName: "LOT-001",
|
|
Quantity: 1,
|
|
UnitPrice: 100.0,
|
|
},
|
|
},
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
exportSvc := services.NewExportService(config.ExportConfig{}, nil, nil)
|
|
handler := NewExportHandler(
|
|
exportSvc,
|
|
&mockConfigService{config: mockConfig},
|
|
nil,
|
|
"testuser",
|
|
)
|
|
|
|
// Create HTTP request
|
|
req, _ := http.NewRequest("GET", "/api/configs/test-uuid/export", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
c.Params = gin.Params{
|
|
{Key: "uuid", Value: "test-uuid"},
|
|
}
|
|
|
|
handler.ExportConfigCSV(c)
|
|
|
|
// Check status code
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("Expected status 200, got %d", w.Code)
|
|
}
|
|
|
|
// Check Content-Type header
|
|
contentType := w.Header().Get("Content-Type")
|
|
if contentType != "text/csv; charset=utf-8" {
|
|
t.Errorf("Expected Content-Type 'text/csv; charset=utf-8', got %q", contentType)
|
|
}
|
|
|
|
// Check for BOM
|
|
responseBody := w.Body.Bytes()
|
|
if len(responseBody) < 3 {
|
|
t.Fatalf("Response too short to contain BOM")
|
|
}
|
|
|
|
expectedBOM := []byte{0xEF, 0xBB, 0xBF}
|
|
actualBOM := responseBody[:3]
|
|
if !bytes.Equal(actualBOM, expectedBOM) {
|
|
t.Errorf("UTF-8 BOM mismatch")
|
|
}
|
|
}
|
|
|
|
func TestExportConfigCSV_NotFound(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
exportSvc := services.NewExportService(config.ExportConfig{}, nil, nil)
|
|
handler := NewExportHandler(
|
|
exportSvc,
|
|
&mockConfigService{err: errors.New("config not found")},
|
|
nil,
|
|
"testuser",
|
|
)
|
|
|
|
req, _ := http.NewRequest("GET", "/api/configs/nonexistent-uuid/export", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
c.Params = gin.Params{
|
|
{Key: "uuid", Value: "nonexistent-uuid"},
|
|
}
|
|
handler.ExportConfigCSV(c)
|
|
|
|
// Should return 404 Not Found
|
|
if w.Code != http.StatusNotFound {
|
|
t.Errorf("Expected status 404, got %d", w.Code)
|
|
}
|
|
|
|
// Should return JSON error
|
|
var errResp map[string]interface{}
|
|
json.Unmarshal(w.Body.Bytes(), &errResp)
|
|
if _, hasError := errResp["error"]; !hasError {
|
|
t.Errorf("Expected error in JSON response")
|
|
}
|
|
}
|
|
|
|
func TestExportConfigCSV_EmptyItems(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
// Mock configuration with empty items
|
|
mockConfig := &models.Configuration{
|
|
UUID: "test-uuid",
|
|
Name: "Empty Config",
|
|
OwnerUsername: "testuser",
|
|
Items: models.ConfigItems{},
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
exportSvc := services.NewExportService(config.ExportConfig{}, nil, nil)
|
|
handler := NewExportHandler(
|
|
exportSvc,
|
|
&mockConfigService{config: mockConfig},
|
|
nil,
|
|
"testuser",
|
|
)
|
|
|
|
req, _ := http.NewRequest("GET", "/api/configs/test-uuid/export", nil)
|
|
w := httptest.NewRecorder()
|
|
|
|
c, _ := gin.CreateTestContext(w)
|
|
c.Request = req
|
|
c.Params = gin.Params{
|
|
{Key: "uuid", Value: "test-uuid"},
|
|
}
|
|
handler.ExportConfigCSV(c)
|
|
|
|
// Should return 400 Bad Request
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("Expected status 400, got %d", w.Code)
|
|
}
|
|
|
|
// Should return JSON error
|
|
var errResp map[string]interface{}
|
|
json.Unmarshal(w.Body.Bytes(), &errResp)
|
|
if _, hasError := errResp["error"]; !hasError {
|
|
t.Errorf("Expected error in JSON response")
|
|
}
|
|
}
|