style(boc): Landvex design system — no emojis, light theme, professional
- Replaced all emojis with SVG icons (Lucide-style) - Changed color scheme to Landvex blue (#0066FF) - Light theme background (#f5f5f7) - Professional typography (Inter, JetBrains Mono) - Clean, minimal design following Design Constitution - No decorative elements — information first - Consistent with Landvex Enterprise platform
This commit is contained in:
+274
@@ -0,0 +1,274 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
baseURL = "http://localhost:9096"
|
||||
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMjIyMjIyMjItMjIyMi0yMjIyLTIyMjItMjIyMjIyMjIyMjIyIiwiZW1haWwiOiJlcmlrQGxhbmR2ZXguY29tIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzgyMzQ0MDAwfQ.demo"
|
||||
)
|
||||
|
||||
// BenchmarkHealthCheck - simple health endpoint
|
||||
func BenchmarkHealthCheck(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
resp, err := http.Get(baseURL + "/health")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkLogin - auth endpoint
|
||||
func BenchmarkLogin(b *testing.B) {
|
||||
payload := map[string]string{
|
||||
"email": "erik@landvex.com",
|
||||
"password": "password123",
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
resp, err := http.Post(baseURL+"/api/v1/auth/login", "application/json", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDashboard - protected endpoint with analytics
|
||||
func BenchmarkDashboard(b *testing.B) {
|
||||
req, _ := http.NewRequest("GET", baseURL+"/api/v1/analytics/dashboard", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkQuotesList - database query
|
||||
func BenchmarkQuotesList(b *testing.B) {
|
||||
req, _ := http.NewRequest("GET", baseURL+"/api/v1/sales/quotes", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Concurrent load test
|
||||
func TestConcurrentLoad(t *testing.T) {
|
||||
concurrency := 50
|
||||
requests := 100
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, concurrency*requests)
|
||||
start := time.Now()
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
wg.Add(1)
|
||||
go func(worker int) {
|
||||
defer wg.Done()
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
|
||||
for j := 0; j < requests; j++ {
|
||||
req, _ := http.NewRequest("GET", baseURL+"/health", nil)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
errors <- fmt.Errorf("worker %d req %d: %v", worker, j, err)
|
||||
continue
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
errors <- fmt.Errorf("worker %d req %d: status %d", worker, j, resp.StatusCode)
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
duration := time.Since(start)
|
||||
totalRequests := concurrency * requests
|
||||
rps := float64(totalRequests) / duration.Seconds()
|
||||
|
||||
errCount := 0
|
||||
for err := range errors {
|
||||
if errCount < 5 {
|
||||
t.Logf("Error: %v", err)
|
||||
}
|
||||
errCount++
|
||||
}
|
||||
|
||||
t.Logf("Total: %d requests in %v (%.0f req/sec)", totalRequests, duration, rps)
|
||||
t.Logf("Errors: %d (%.2f%%)", errCount, float64(errCount)/float64(totalRequests)*100)
|
||||
|
||||
if errCount > totalRequests/10 {
|
||||
t.Fatalf("Too many errors: %d", errCount)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFullWorkflow - complete business flow
|
||||
func TestFullWorkflow(t *testing.T) {
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
|
||||
// 1. Login
|
||||
loginPayload := map[string]string{
|
||||
"email": "erik@landvex.com",
|
||||
"password": "password123",
|
||||
}
|
||||
body, _ := json.Marshal(loginPayload)
|
||||
resp, err := client.Post(baseURL+"/api/v1/auth/login", "application/json", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
t.Fatalf("Login failed: %v", err)
|
||||
}
|
||||
var loginResp map[string]interface{}
|
||||
json.NewDecoder(resp.Body).Decode(&loginResp)
|
||||
resp.Body.Close()
|
||||
|
||||
authToken, ok := loginResp["token"].(string)
|
||||
if !ok {
|
||||
t.Fatal("No token in response")
|
||||
}
|
||||
t.Logf("✓ Login successful")
|
||||
|
||||
// 2. Create customer
|
||||
customerPayload := map[string]interface{}{
|
||||
"name": "Stress Test AB",
|
||||
"email": "stress@test.com",
|
||||
"phone": "+46701234567",
|
||||
"address": "Testgatan 1, Stockholm",
|
||||
}
|
||||
body, _ = json.Marshal(customerPayload)
|
||||
req, _ := http.NewRequest("POST", baseURL+"/api/v1/crm/customers", bytes.NewReader(body))
|
||||
req.Header.Set("Authorization", "Bearer "+authToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Create customer failed: %v", err)
|
||||
}
|
||||
var customerResp map[string]interface{}
|
||||
json.NewDecoder(resp.Body).Decode(&customerResp)
|
||||
resp.Body.Close()
|
||||
customerID := customerResp["id"].(string)
|
||||
t.Logf("✓ Customer created: %s", customerID)
|
||||
|
||||
// 3. Create quote
|
||||
quotePayload := map[string]interface{}{
|
||||
"customer_id": customerID,
|
||||
"title": "Stress Test Quote",
|
||||
"items": []map[string]interface{}{
|
||||
{
|
||||
"description": "Test Product",
|
||||
"quantity": 10,
|
||||
"unit_price": 1000.00,
|
||||
"tax_rate": 25.0,
|
||||
},
|
||||
},
|
||||
}
|
||||
body, _ = json.Marshal(quotePayload)
|
||||
req, _ = http.NewRequest("POST", baseURL+"/api/v1/sales/quotes", bytes.NewReader(body))
|
||||
req.Header.Set("Authorization", "Bearer "+authToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Create quote failed: %v", err)
|
||||
}
|
||||
var quoteResp map[string]interface{}
|
||||
json.NewDecoder(resp.Body).Decode("eResp)
|
||||
resp.Body.Close()
|
||||
quoteID := quoteResp["id"].(string)
|
||||
t.Logf("✓ Quote created: %s", quoteID)
|
||||
|
||||
// 4. Accept quote
|
||||
req, _ = http.NewRequest("POST", baseURL+"/api/v1/sales/quotes/"+quoteID+"/accept", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+authToken)
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Accept quote failed: %v", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
t.Logf("✓ Quote accepted")
|
||||
|
||||
// 5. Convert to order
|
||||
req, _ = http.NewRequest("POST", baseURL+"/api/v1/sales/quotes/"+quoteID+"/convert", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+authToken)
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Convert quote failed: %v", err)
|
||||
}
|
||||
var orderResp map[string]interface{}
|
||||
json.NewDecoder(resp.Body).Decode(&orderResp)
|
||||
resp.Body.Close()
|
||||
t.Logf("✓ Quote converted to order: %s", orderResp["order_id"])
|
||||
|
||||
// 6. Get dashboard
|
||||
req, _ = http.NewRequest("GET", baseURL+"/api/v1/analytics/dashboard", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+authToken)
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Dashboard failed: %v", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
t.Logf("✓ Dashboard loaded")
|
||||
|
||||
t.Logf("\n=== WORKFLOW COMPLETE ===")
|
||||
}
|
||||
|
||||
// TestPDFGeneration - stress PDF generation
|
||||
func TestPDFGeneration(t *testing.T) {
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
|
||||
// Get existing quote
|
||||
req, _ := http.NewRequest("GET", baseURL+"/api/v1/sales/quotes", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("List quotes failed: %v", err)
|
||||
}
|
||||
var quotesResp map[string]interface{}
|
||||
json.NewDecoder(resp.Body).Decode("esResp)
|
||||
resp.Body.Close()
|
||||
|
||||
quotes := quotesResp["quotes"].([]interface{})
|
||||
if len(quotes) == 0 {
|
||||
t.Skip("No quotes to test")
|
||||
}
|
||||
|
||||
quoteID := quotes[0].(map[string]interface{})["id"].(string)
|
||||
|
||||
// Generate PDF
|
||||
start := time.Now()
|
||||
req, _ = http.NewRequest("GET", baseURL+"/api/v1/sales/quotes/"+quoteID+"/pdf", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("PDF generation failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("PDF generation returned %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
duration := time.Since(start)
|
||||
t.Logf("✓ PDF generated in %v (status: %d, content-type: %s)", duration, resp.StatusCode, resp.Header.Get("Content-Type"))
|
||||
}
|
||||
Reference in New Issue
Block a user