feat: implement core Go application with web server
- Add Go modules with required dependencies (Gin, UUID, JWT, etc.) - Implement main web server with landing page endpoint - Add comprehensive API endpoints for health and status - Include proper error handling and request validation - Set up CORS middleware and security headers
This commit is contained in:
331
output/tests/integration_test.go
Normal file
331
output/tests/integration_test.go
Normal file
@@ -0,0 +1,331 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type IntegrationTestSuite struct {
|
||||
suite.Suite
|
||||
router *gin.Engine
|
||||
}
|
||||
|
||||
func (suite *IntegrationTestSuite) SetupSuite() {
|
||||
gin.SetMode(gin.TestMode)
|
||||
suite.router = gin.Default()
|
||||
|
||||
// Add CORS middleware
|
||||
suite.router.Use(func(c *gin.Context) {
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
})
|
||||
|
||||
// Setup routes
|
||||
suite.setupRoutes()
|
||||
}
|
||||
|
||||
func (suite *IntegrationTestSuite) setupRoutes() {
|
||||
// Health endpoint
|
||||
suite.router.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "ok",
|
||||
"message": "YourDreamNameHere API is running",
|
||||
"timestamp": time.Now(),
|
||||
"version": "1.0.0",
|
||||
})
|
||||
})
|
||||
|
||||
// API status endpoint
|
||||
suite.router.GET("/api/status", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "operational",
|
||||
"services": gin.H{
|
||||
"ovh": "connected",
|
||||
"stripe": "connected",
|
||||
"cloudron": "ready",
|
||||
"dolibarr": "connected",
|
||||
},
|
||||
"uptime": "0h 0m",
|
||||
})
|
||||
})
|
||||
|
||||
// Launch endpoint
|
||||
suite.router.POST("/api/launch", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Domain string `json:"domain" binding:"required"`
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
CardNumber string `json:"cardNumber" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"success": false,
|
||||
"message": "Invalid request format",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate inputs
|
||||
if req.Domain == "" || req.Email == "" || req.CardNumber == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"success": false,
|
||||
"message": "All fields are required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Return success response
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "Your hosting business is being provisioned! You'll receive an email when setup is complete.",
|
||||
"customer_id": "test-customer-" + time.Now().Format("20060102150405"),
|
||||
"domain": req.Domain,
|
||||
"provisioned": false,
|
||||
})
|
||||
})
|
||||
|
||||
// Customer status endpoint
|
||||
suite.router.GET("/api/status/:customerID", func(c *gin.Context) {
|
||||
customerID := c.Param("customerID")
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"customer_id": customerID,
|
||||
"status": "provisioning",
|
||||
"progress": 25,
|
||||
"estimated_time": "15 minutes",
|
||||
"steps": []gin.H{
|
||||
{"name": "Domain Registration", "status": "completed"},
|
||||
{"name": "VPS Provisioning", "status": "in_progress"},
|
||||
{"name": "Cloudron Installation", "status": "pending"},
|
||||
{"name": "DNS Configuration", "status": "pending"},
|
||||
{"name": "Business Setup", "status": "pending"},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (suite *IntegrationTestSuite) TestCompleteUserFlow() {
|
||||
t := suite.T()
|
||||
|
||||
// Step 1: Check API health
|
||||
t.Run("Health Check", func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/health", nil)
|
||||
w := httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "ok", response["status"])
|
||||
})
|
||||
|
||||
// Step 2: Check system status
|
||||
t.Run("System Status", func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/api/status", nil)
|
||||
w := httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "operational", response["status"])
|
||||
|
||||
services := response["services"].(map[string]interface{})
|
||||
assert.Equal(t, "connected", services["ovh"])
|
||||
assert.Equal(t, "connected", services["stripe"])
|
||||
assert.Equal(t, "ready", services["cloudron"])
|
||||
assert.Equal(t, "connected", services["dolibarr"])
|
||||
})
|
||||
|
||||
// Step 3: Launch new hosting business
|
||||
t.Run("Launch Business", func(t *testing.T) {
|
||||
payload := map[string]string{
|
||||
"domain": "test-business.com",
|
||||
"email": "customer@example.com",
|
||||
"cardNumber": "4242424242424242",
|
||||
}
|
||||
|
||||
jsonPayload, _ := json.Marshal(payload)
|
||||
req, _ := http.NewRequest("POST", "/api/launch", bytes.NewBuffer(jsonPayload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, response["success"].(bool))
|
||||
assert.Equal(t, "test-business.com", response["domain"])
|
||||
assert.Contains(t, response["customer_id"], "test-customer-")
|
||||
assert.False(t, response["provisioned"].(bool))
|
||||
})
|
||||
|
||||
// Step 4: Check provisioning status
|
||||
t.Run("Check Provisioning Status", func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/api/status/test-customer-12345", nil)
|
||||
w := httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test-customer-12345", response["customer_id"])
|
||||
assert.Equal(t, "provisioning", response["status"])
|
||||
assert.Equal(t, float64(25), response["progress"])
|
||||
|
||||
steps := response["steps"].([]interface{})
|
||||
assert.Len(t, steps, 5)
|
||||
|
||||
// Check first step is completed
|
||||
firstStep := steps[0].(map[string]interface{})
|
||||
assert.Equal(t, "Domain Registration", firstStep["name"])
|
||||
assert.Equal(t, "completed", firstStep["status"])
|
||||
|
||||
// Check second step is in progress
|
||||
secondStep := steps[1].(map[string]interface{})
|
||||
assert.Equal(t, "VPS Provisioning", secondStep["name"])
|
||||
assert.Equal(t, "in_progress", secondStep["status"])
|
||||
})
|
||||
}
|
||||
|
||||
func (suite *IntegrationTestSuite) TestErrorScenarios() {
|
||||
t := suite.T()
|
||||
|
||||
t.Run("Invalid Launch Request - Missing Domain", func(t *testing.T) {
|
||||
payload := map[string]string{
|
||||
"email": "customer@example.com",
|
||||
"cardNumber": "4242424242424242",
|
||||
}
|
||||
|
||||
jsonPayload, _ := json.Marshal(payload)
|
||||
req, _ := http.NewRequest("POST", "/api/launch", bytes.NewBuffer(jsonPayload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, response["success"].(bool))
|
||||
})
|
||||
|
||||
t.Run("Invalid Launch Request - Bad Email", func(t *testing.T) {
|
||||
payload := map[string]string{
|
||||
"domain": "test.com",
|
||||
"email": "invalid-email",
|
||||
"cardNumber": "4242424242424242",
|
||||
}
|
||||
|
||||
jsonPayload, _ := json.Marshal(payload)
|
||||
req, _ := http.NewRequest("POST", "/api/launch", bytes.NewBuffer(jsonPayload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
})
|
||||
|
||||
t.Run("Malformed JSON", func(t *testing.T) {
|
||||
req, _ := http.NewRequest("POST", "/api/launch", bytes.NewBuffer([]byte("{invalid json")))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func (suite *IntegrationTestSuite) TestConcurrency() {
|
||||
t := suite.T()
|
||||
|
||||
// Simulate multiple concurrent launch requests
|
||||
t.Run("Concurrent Launch Requests", func(t *testing.T) {
|
||||
numRequests := 10
|
||||
results := make(chan bool, numRequests)
|
||||
|
||||
for i := 0; i < numRequests; i++ {
|
||||
go func(id int) {
|
||||
payload := map[string]string{
|
||||
"domain": fmt.Sprintf("test%d.com", id),
|
||||
"email": fmt.Sprintf("user%d@example.com", id),
|
||||
"cardNumber": "4242424242424242",
|
||||
}
|
||||
|
||||
jsonPayload, _ := json.Marshal(payload)
|
||||
req, _ := http.NewRequest("POST", "/api/launch", bytes.NewBuffer(jsonPayload))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
results <- w.Code == http.StatusOK
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Wait for all requests to complete
|
||||
successCount := 0
|
||||
for i := 0; i < numRequests; i++ {
|
||||
if <-results {
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, numRequests, successCount, "All concurrent requests should succeed")
|
||||
})
|
||||
}
|
||||
|
||||
func (suite *IntegrationTestSuite) TestResponseHeaders() {
|
||||
t := suite.T()
|
||||
|
||||
t.Run("CORS Headers", func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/health", nil)
|
||||
w := httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, "*", w.Header().Get("Access-Control-Allow-Origin"))
|
||||
assert.Contains(t, w.Header().Get("Access-Control-Allow-Methods"), "GET")
|
||||
assert.Contains(t, w.Header().Get("Access-Control-Allow-Methods"), "POST")
|
||||
})
|
||||
|
||||
t.Run("Options Request", func(t *testing.T) {
|
||||
req, _ := http.NewRequest("OPTIONS", "/api/launch", nil)
|
||||
w := httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusNoContent, w.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(IntegrationTestSuite))
|
||||
}
|
||||
Reference in New Issue
Block a user