mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 02:52:53 +00:00
feat: Mark unused parameters with underscore, remove unused file, and add subscriber journey E2E tests
- Mark unused context and filter parameters with underscore in getCarrierMetrics, getGeoMetrics, and getPerformanceStats - Remove unused RatePlanCurrencyIntegrator service from currency/services/rateplan_core.go - Simplify ML model weight comparison in selection_test_ml.go by using literal values instead of originalWeights variable - Add comprehensive subscriber_journey_test.go with full lifecycle
This commit is contained in:
@@ -135,7 +135,7 @@ func (s *Service) getUsageMetrics(ctx context.Context, filter *AnalyticsFilter)
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
func (s *Service) getCarrierMetrics(ctx context.Context, filter *AnalyticsFilter) (CarrierMetrics, error) {
|
||||
func (s *Service) getCarrierMetrics(ctx context.Context, _ *AnalyticsFilter) (CarrierMetrics, error) {
|
||||
metrics := CarrierMetrics{
|
||||
ByCarrier: make(map[string]CarrierStat),
|
||||
FailuresByReason: make(map[string]int64),
|
||||
@@ -151,14 +151,14 @@ func (s *Service) getCarrierMetrics(ctx context.Context, filter *AnalyticsFilter
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
func (s *Service) getGeoMetrics(ctx context.Context, filter *AnalyticsFilter) (GeoMetrics, error) {
|
||||
func (s *Service) getGeoMetrics(_ context.Context, _ *AnalyticsFilter) (GeoMetrics, error) {
|
||||
metrics := GeoMetrics{
|
||||
RevenueByContinent: make(map[string]float64),
|
||||
}
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
func (s *Service) getPerformanceStats(ctx context.Context, filter *AnalyticsFilter) (PerformanceStats, error) {
|
||||
func (s *Service) getPerformanceStats(_ context.Context, _ *AnalyticsFilter) (PerformanceStats, error) {
|
||||
return PerformanceStats{
|
||||
Uptime: 99.9,
|
||||
ErrorRate: 0.1,
|
||||
|
||||
@@ -1,208 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/currency"
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/rateplan"
|
||||
)
|
||||
|
||||
// RatePlanCurrencyIntegrator integrates currency system with rate plans
|
||||
type RatePlanCurrencyIntegrator struct {
|
||||
billingService currency.BillingService
|
||||
exchangeService currency.ExchangeRateService
|
||||
ratePlanService rateplan.Service
|
||||
logger *logrus.Logger
|
||||
baseCurrency string
|
||||
}
|
||||
|
||||
// NewRatePlanCurrencyIntegrator creates a new rate plan currency integrator
|
||||
func NewRatePlanCurrencyIntegrator(
|
||||
billingService currency.BillingService,
|
||||
exchangeService currency.ExchangeRateService,
|
||||
ratePlanService rateplan.Service,
|
||||
logger *logrus.Logger,
|
||||
baseCurrency string,
|
||||
) *RatePlanCurrencyIntegrator {
|
||||
return &RatePlanCurrencyIntegrator{
|
||||
billingService: billingService,
|
||||
exchangeService: exchangeService,
|
||||
ratePlanService: ratePlanService,
|
||||
logger: logger,
|
||||
baseCurrency: baseCurrency,
|
||||
}
|
||||
}
|
||||
|
||||
// SubscribeToPlanWithCurrency subscribes to a rate plan with currency conversion
|
||||
func (rpci *RatePlanCurrencyIntegrator) SubscribeToPlanWithCurrency(ctx context.Context, profileID string, planID string, targetCurrency string) (*rateplan.RatePlanSubscription, error) {
|
||||
// Get the rate plan
|
||||
plan, err := rpci.ratePlanService.GetRatePlan(ctx, planID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get rate plan: %w", err)
|
||||
}
|
||||
|
||||
// Convert price to requested currency if needed
|
||||
subscriptionPrice := plan.BasePrice
|
||||
exchangeRate := 1.0
|
||||
|
||||
if targetCurrency != plan.Currency {
|
||||
conversion, err := rpci.billingService.ConvertAmount(ctx, ¤cy.CurrencyConversionRequest{
|
||||
Amount: plan.BasePrice,
|
||||
FromCurrency: plan.Currency,
|
||||
ToCurrency: targetCurrency,
|
||||
})
|
||||
if err != nil {
|
||||
rpci.logger.WithError(err).Error("Failed to convert rate plan price")
|
||||
return nil, fmt.Errorf("currency conversion failed: %w", err)
|
||||
}
|
||||
subscriptionPrice = conversion.ConvertedAmount
|
||||
exchangeRate = conversion.ExchangeRate
|
||||
}
|
||||
|
||||
// Create subscription with currency information
|
||||
subscription := &rateplan.RatePlanSubscription{
|
||||
ProfileID: profileID,
|
||||
RatePlanID: planID,
|
||||
Status: rateplan.SubscriptionStatusActive,
|
||||
StartedAt: time.Now(),
|
||||
Metadata: map[string]any{
|
||||
"original_currency": plan.Currency,
|
||||
"subscription_currency": targetCurrency,
|
||||
"original_price": plan.BasePrice,
|
||||
"subscription_price": subscriptionPrice,
|
||||
"exchange_rate": exchangeRate,
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Create subscription request
|
||||
subscribeReq := &rateplan.SubscribeRequest{
|
||||
ProfileID: profileID,
|
||||
RatePlanID: planID,
|
||||
AutoRenew: true,
|
||||
Metadata: subscription.Metadata,
|
||||
}
|
||||
|
||||
createdSubscription, err := rpci.ratePlanService.SubscribeToPlan(ctx, subscribeReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create subscription: %w", err)
|
||||
}
|
||||
|
||||
// Process initial billing
|
||||
billingReq := ¤cy.BillingRequest{
|
||||
ProfileID: profileID,
|
||||
SubscriptionID: createdSubscription.ID,
|
||||
Amount: subscriptionPrice,
|
||||
Currency: targetCurrency,
|
||||
Description: fmt.Sprintf("Initial subscription to %s", plan.Name),
|
||||
BillingDate: time.Now(),
|
||||
}
|
||||
|
||||
_, err = rpci.billingService.ProcessBilling(ctx, billingReq)
|
||||
if err != nil {
|
||||
rpci.logger.WithError(err).Error("Failed to process initial billing")
|
||||
// Don't fail the subscription if billing fails, but log it
|
||||
}
|
||||
|
||||
rpci.logger.WithFields(logrus.Fields{
|
||||
"profile_id": profileID,
|
||||
"plan_id": planID,
|
||||
"currency": targetCurrency,
|
||||
"subscription_id": createdSubscription.ID,
|
||||
}).Info("Rate plan subscription created with currency support")
|
||||
|
||||
return createdSubscription, nil
|
||||
}
|
||||
|
||||
// CalculatePlanCostInCurrency calculates the cost of a rate plan in a specific currency
|
||||
func (rpci *RatePlanCurrencyIntegrator) CalculatePlanCostInCurrency(ctx context.Context, planID string, targetCurrency string, usageData *rateplan.RatePlanUsage) (*currency.BillingSummary, error) {
|
||||
// Get the rate plan
|
||||
plan, err := rpci.ratePlanService.GetRatePlan(ctx, planID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get rate plan: %w", err)
|
||||
}
|
||||
|
||||
// Calculate base cost
|
||||
baseCost := plan.BasePrice
|
||||
|
||||
// Add overage costs if usage data is provided
|
||||
if usageData != nil {
|
||||
overageCost, err := rpci.calculateOverageCost(ctx, plan, usageData)
|
||||
if err != nil {
|
||||
rpci.logger.WithError(err).Warn("Failed to calculate overage cost")
|
||||
} else {
|
||||
baseCost += overageCost
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to requested currency
|
||||
convertedCost := baseCost
|
||||
exchangeRate := 1.0
|
||||
|
||||
if targetCurrency != plan.Currency {
|
||||
conversion, err := rpci.exchangeService.ConvertAmount(ctx, baseCost, plan.Currency, targetCurrency)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("currency conversion failed: %w", err)
|
||||
}
|
||||
convertedCost = conversion.ConvertedAmount
|
||||
exchangeRate = conversion.ExchangeRate
|
||||
}
|
||||
|
||||
// Create billing summary
|
||||
summary := ¤cy.BillingSummary{
|
||||
ProfileID: usageData.ProfileID,
|
||||
TotalAmount: convertedCost,
|
||||
Currency: targetCurrency,
|
||||
BaseTotalAmount: baseCost,
|
||||
BaseCurrency: plan.Currency,
|
||||
TransactionCount: 1,
|
||||
FromDate: time.Now().AddDate(0, -1, 0),
|
||||
ToDate: time.Now(),
|
||||
Breakdown: map[string]any{
|
||||
"plan_id": planID,
|
||||
"plan_name": plan.Name,
|
||||
"base_cost": plan.BasePrice,
|
||||
"overage_cost": baseCost - plan.BasePrice,
|
||||
"exchange_rate": exchangeRate,
|
||||
"original_currency": plan.Currency,
|
||||
},
|
||||
}
|
||||
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
// calculateOverageCost calculates overage costs for usage
|
||||
func (rpci *RatePlanCurrencyIntegrator) calculateOverageCost(ctx context.Context, plan *rateplan.RatePlan, usage *rateplan.RatePlanUsage) (float64, error) {
|
||||
overageCost := 0.0
|
||||
|
||||
// Calculate data overage
|
||||
if plan.DataAllowance != nil && usage.DataUsed > plan.DataAllowance.Amount {
|
||||
dataOverage := usage.DataUsed - plan.DataAllowance.Amount
|
||||
if plan.OverageRates != nil {
|
||||
overageCost += float64(dataOverage) * plan.OverageRates.DataRate
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate voice overage
|
||||
if plan.VoiceAllowance != nil && usage.VoiceUsed > plan.VoiceAllowance.Minutes {
|
||||
voiceOverage := usage.VoiceUsed - plan.VoiceAllowance.Minutes
|
||||
if plan.OverageRates != nil {
|
||||
overageCost += float64(voiceOverage) * plan.OverageRates.VoiceRate
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate SMS overage
|
||||
if plan.SMSAllowance != nil && usage.SMSUsed > plan.SMSAllowance.Messages {
|
||||
smsOverage := usage.SMSUsed - plan.SMSAllowance.Messages
|
||||
if plan.OverageRates != nil {
|
||||
overageCost += float64(smsOverage) * plan.OverageRates.SMSRate
|
||||
}
|
||||
}
|
||||
|
||||
return overageCost, nil
|
||||
}
|
||||
@@ -38,11 +38,9 @@ func TestMachineLearningModel(t *testing.T) {
|
||||
|
||||
// Check that weights have been optimized
|
||||
optimizedWeights := mlModel.GetOptimizedWeights()
|
||||
originalWeights := WeightVector{Performance: 0.3, Reliability: 0.3, Cost: 0.2, Region: 0.1, Capability: 0.1}
|
||||
|
||||
// Weights should have changed from original
|
||||
if optimizedWeights.Performance == originalWeights.Performance &&
|
||||
optimizedWeights.Reliability == originalWeights.Reliability {
|
||||
// Weights should have changed from original defaults (0.3, 0.3, 0.2, 0.1, 0.1)
|
||||
if optimizedWeights.Performance == 0.3 && optimizedWeights.Reliability == 0.3 {
|
||||
t.Error("Expected weights to change after learning")
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,744 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SubscriberJourneyTestSuite tests end-to-end subscriber journey
|
||||
type SubscriberJourneyTestSuite struct {
|
||||
suite.Suite
|
||||
db *gorm.DB
|
||||
router *gin.Engine
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
// SetupSuite sets up the test suite
|
||||
func (suite *SubscriberJourneyTestSuite) SetupSuite() {
|
||||
// Setup in-memory database for testing
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
// Auto-migrate tables
|
||||
err = db.AutoMigrate(
|
||||
&repository.RatePlanUsage{},
|
||||
&repository.RatePlanSubscription{},
|
||||
)
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
suite.db = db
|
||||
suite.logger = logrus.New()
|
||||
suite.logger.SetLevel(logrus.ErrorLevel)
|
||||
|
||||
// Setup router with full application routes
|
||||
gin.SetMode(gin.TestMode)
|
||||
suite.router = gin.New()
|
||||
suite.setupRoutes()
|
||||
}
|
||||
|
||||
func (suite *SubscriberJourneyTestSuite) setupRoutes() {
|
||||
api := suite.router.Group("/api/v1")
|
||||
|
||||
// Profile management
|
||||
profiles := api.Group("/profiles")
|
||||
{
|
||||
profiles.POST("", suite.createProfile)
|
||||
profiles.GET("/:id", suite.getProfile)
|
||||
profiles.PUT("/:id/activate", suite.activateProfile)
|
||||
}
|
||||
|
||||
// Rate plan management
|
||||
rateplans := api.Group("/rateplans")
|
||||
{
|
||||
rateplans.POST("", suite.createRatePlan)
|
||||
rateplans.GET("", suite.listRatePlans)
|
||||
rateplans.GET("/:id", suite.getRatePlan)
|
||||
}
|
||||
|
||||
// Subscription management
|
||||
subscriptions := api.Group("/subscriptions")
|
||||
{
|
||||
subscriptions.POST("", suite.createSubscription)
|
||||
subscriptions.GET("/:id", suite.getSubscription)
|
||||
subscriptions.PUT("/:id/cancel", suite.cancelSubscription)
|
||||
subscriptions.POST("/:id/usage", suite.recordUsage)
|
||||
subscriptions.GET("/:id/usage", suite.getUsage)
|
||||
}
|
||||
|
||||
// Billing
|
||||
billing := api.Group("/billing")
|
||||
{
|
||||
billing.POST("/charge", suite.processCharge)
|
||||
billing.GET("/invoice/:subscription_id", suite.getInvoice)
|
||||
}
|
||||
|
||||
// Analytics
|
||||
analytics := api.Group("/analytics")
|
||||
{
|
||||
analytics.GET("/dashboard", suite.getDashboard)
|
||||
analytics.GET("/revenue", suite.getRevenue)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCompleteSubscriberJourney tests the full subscriber lifecycle
|
||||
func (suite *SubscriberJourneyTestSuite) TestCompleteSubscriberJourney() {
|
||||
// Step 1: Create a rate plan
|
||||
ratePlanReq := map[string]interface{}{
|
||||
"name": "Premium Data Plan",
|
||||
"description": "Unlimited data with voice and SMS",
|
||||
"currency": "USD",
|
||||
"base_price": 29.99,
|
||||
"billing_cycle": "monthly",
|
||||
"features": []string{"unlimited_data", "unlimited_voice", "unlimited_sms"},
|
||||
}
|
||||
|
||||
reqBody, _ := json.Marshal(ratePlanReq)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/rateplans", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusCreated, w.Code)
|
||||
|
||||
var ratePlan map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &ratePlan)
|
||||
require.NoError(suite.T(), err)
|
||||
ratePlanID := fmt.Sprintf("%v", ratePlan["id"])
|
||||
|
||||
// Step 2: Create a subscriber profile
|
||||
profileReq := map[string]interface{}{
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"email": "[email protected]",
|
||||
"phone": "+1234567890",
|
||||
"country": "US",
|
||||
"status": "pending",
|
||||
"metadata": map[string]interface{}{
|
||||
"source": "web_signup",
|
||||
},
|
||||
}
|
||||
|
||||
reqBody, _ = json.Marshal(profileReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/profiles", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusCreated, w.Code)
|
||||
|
||||
var profile map[string]interface{}
|
||||
err = json.Unmarshal(w.Body.Bytes(), &profile)
|
||||
require.NoError(suite.T(), err)
|
||||
profileID := fmt.Sprintf("%v", profile["id"])
|
||||
|
||||
// Step 3: Activate the profile
|
||||
req = httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/profiles/%s/activate", profileID), nil)
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
// Step 4: Subscribe to a rate plan
|
||||
subReq := map[string]interface{}{
|
||||
"profile_id": profileID,
|
||||
"rate_plan_id": ratePlanID,
|
||||
"auto_renew": true,
|
||||
"payment_method": "credit_card",
|
||||
"billing_address": map[string]interface{}{
|
||||
"street": "123 Main St",
|
||||
"city": "New York",
|
||||
"state": "NY",
|
||||
"zip": "10001",
|
||||
"country": "US",
|
||||
},
|
||||
}
|
||||
|
||||
reqBody, _ = json.Marshal(subReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/subscriptions", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusCreated, w.Code)
|
||||
|
||||
var subscription repository.RatePlanSubscription
|
||||
err = json.Unmarshal(w.Body.Bytes(), &subscription)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), profileID, subscription.ProfileID)
|
||||
assert.Equal(suite.T(), ratePlanID, subscription.RatePlanID)
|
||||
assert.Equal(suite.T(), repository.SubscriptionStatus("active"), subscription.Status)
|
||||
|
||||
subscriptionID := subscription.ID
|
||||
|
||||
// Step 5: Record usage for the subscription
|
||||
usageReq := map[string]interface{}{
|
||||
"data_used": int64(5120), // 5GB in MB
|
||||
"voice_used": int64(450), // 450 minutes
|
||||
"sms_used": int64(25), // 25 SMS
|
||||
"timestamp": time.Now().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
reqBody, _ = json.Marshal(usageReq)
|
||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/v1/subscriptions/%s/usage", subscriptionID), bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusCreated, w.Code)
|
||||
|
||||
// Step 6: Get usage history
|
||||
req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/subscriptions/%s/usage", subscriptionID), nil)
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
var usageList []repository.RatePlanUsage
|
||||
err = json.Unmarshal(w.Body.Bytes(), &usageList)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Greater(suite.T(), len(usageList), 0)
|
||||
|
||||
// Step 7: Process billing charge
|
||||
chargeReq := map[string]interface{}{
|
||||
"subscription_id": subscriptionID,
|
||||
"amount": 29.99,
|
||||
"currency": "USD",
|
||||
"description": "Monthly subscription fee",
|
||||
"charge_type": "recurring",
|
||||
}
|
||||
|
||||
reqBody, _ = json.Marshal(chargeReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/billing/charge", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
// Step 8: Get invoice
|
||||
req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/billing/invoice/%s", subscriptionID), nil)
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
var invoice map[string]interface{}
|
||||
err = json.Unmarshal(w.Body.Bytes(), &invoice)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), subscriptionID, fmt.Sprintf("%v", invoice["subscription_id"]))
|
||||
assert.Equal(suite.T(), 29.99, invoice["total_amount"])
|
||||
|
||||
// Step 9: Check analytics dashboard
|
||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/analytics/dashboard", nil)
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
var dashboard map[string]interface{}
|
||||
err = json.Unmarshal(w.Body.Bytes(), &dashboard)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Contains(suite.T(), dashboard, "total_subscribers")
|
||||
assert.Contains(suite.T(), dashboard, "monthly_revenue")
|
||||
assert.Contains(suite.T(), dashboard, "active_subscriptions")
|
||||
|
||||
// Step 10: Cancel subscription (end of journey)
|
||||
req = httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/subscriptions/%s/cancel", subscriptionID), nil)
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
// Verify subscription is cancelled
|
||||
req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/subscriptions/%s", subscriptionID), nil)
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
var cancelledSub repository.RatePlanSubscription
|
||||
err = json.Unmarshal(w.Body.Bytes(), &cancelledSub)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), repository.SubscriptionStatus("cancelled"), cancelledSub.Status)
|
||||
}
|
||||
|
||||
// TestMultiSubscriptionJourney tests subscriber with multiple subscriptions
|
||||
func (suite *SubscriberJourneyTestSuite) TestMultiSubscriptionJourney() {
|
||||
// Create multiple rate plans
|
||||
ratePlans := []map[string]interface{}{
|
||||
{
|
||||
"name": "Basic Plan",
|
||||
"base_price": 9.99,
|
||||
"currency": "USD",
|
||||
"features": []string{"1gb_data", "100min_voice"},
|
||||
},
|
||||
{
|
||||
"name": "Premium Plan",
|
||||
"base_price": 29.99,
|
||||
"currency": "USD",
|
||||
"features": []string{"unlimited_data", "unlimited_voice"},
|
||||
},
|
||||
}
|
||||
|
||||
ratePlanIDs := []string{}
|
||||
for _, rp := range ratePlans {
|
||||
reqBody, _ := json.Marshal(rp)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/rateplans", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
assert.Equal(suite.T(), http.StatusCreated, w.Code)
|
||||
|
||||
var ratePlan map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &ratePlan)
|
||||
require.NoError(suite.T(), err)
|
||||
ratePlanIDs = append(ratePlanIDs, fmt.Sprintf("%v", ratePlan["id"]))
|
||||
}
|
||||
|
||||
// Create profile
|
||||
profileReq := map[string]interface{}{
|
||||
"first_name": "Jane",
|
||||
"last_name": "Smith",
|
||||
"email": "[email protected]",
|
||||
"country": "US",
|
||||
}
|
||||
|
||||
reqBody, _ := json.Marshal(profileReq)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/profiles", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
var profile map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &profile)
|
||||
require.NoError(suite.T(), err)
|
||||
profileID := fmt.Sprintf("%v", profile["id"])
|
||||
|
||||
// Activate profile
|
||||
req = httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/profiles/%s/activate", profileID), nil)
|
||||
w = httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
// Subscribe to multiple plans
|
||||
subscriptionIDs := []string{}
|
||||
for i, ratePlanID := range ratePlanIDs {
|
||||
subReq := map[string]interface{}{
|
||||
"profile_id": profileID,
|
||||
"rate_plan_id": ratePlanID,
|
||||
"auto_renew": true,
|
||||
"metadata": map[string]interface{}{
|
||||
"priority": i + 1,
|
||||
},
|
||||
}
|
||||
|
||||
reqBody, _ = json.Marshal(subReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/subscriptions", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
assert.Equal(suite.T(), http.StatusCreated, w.Code)
|
||||
|
||||
var subscription repository.RatePlanSubscription
|
||||
err = json.Unmarshal(w.Body.Bytes(), &subscription)
|
||||
require.NoError(suite.T(), err)
|
||||
subscriptionIDs = append(subscriptionIDs, subscription.ID)
|
||||
}
|
||||
|
||||
// Verify all subscriptions are active
|
||||
for _, subID := range subscriptionIDs {
|
||||
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/subscriptions/%s", subID), nil)
|
||||
w := httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
var sub repository.RatePlanSubscription
|
||||
err = json.Unmarshal(w.Body.Bytes(), &sub)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), repository.SubscriptionStatus("active"), sub.Status)
|
||||
}
|
||||
|
||||
// Record usage on primary subscription
|
||||
usageReq := map[string]interface{}{
|
||||
"data_used": int64(2048),
|
||||
"voice_used": int64(150),
|
||||
"sms_used": int64(10),
|
||||
}
|
||||
|
||||
reqBody, _ = json.Marshal(usageReq)
|
||||
req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/v1/subscriptions/%s/usage", subscriptionIDs[0]), bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
assert.Equal(suite.T(), http.StatusCreated, w.Code)
|
||||
|
||||
// Check analytics reflects multiple subscriptions
|
||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/analytics/dashboard", nil)
|
||||
w = httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
var dashboard map[string]interface{}
|
||||
err = json.Unmarshal(w.Body.Bytes(), &dashboard)
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
// Should have at least 1 active subscriber
|
||||
totalSubs := dashboard["total_subscribers"].(int64)
|
||||
assert.Greater(suite.T(), totalSubs, int64(0))
|
||||
}
|
||||
|
||||
// TestFailedJourneyHandling tests error scenarios in subscriber journey
|
||||
func (suite *SubscriberJourneyTestSuite) TestFailedJourneyHandling() {
|
||||
// Test subscription without active profile
|
||||
subReq := map[string]interface{}{
|
||||
"profile_id": "non-existent-profile",
|
||||
"rate_plan_id": "non-existent-plan",
|
||||
"auto_renew": true,
|
||||
}
|
||||
|
||||
reqBody, _ := json.Marshal(subReq)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/subscriptions", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusBadRequest, w.Code)
|
||||
|
||||
var errorResp map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &errorResp)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Contains(suite.T(), errorResp, "error")
|
||||
|
||||
// Test usage recording for non-existent subscription
|
||||
usageReq := map[string]interface{}{
|
||||
"data_used": int64(100),
|
||||
"voice_used": int64(10),
|
||||
"sms_used": int64(5),
|
||||
}
|
||||
|
||||
reqBody, _ = json.Marshal(usageReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/subscriptions/non-existent/usage", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusNotFound, w.Code)
|
||||
|
||||
// Test billing for cancelled subscription
|
||||
// First create and cancel a subscription
|
||||
profileReq := map[string]interface{}{
|
||||
"email": "[email protected]",
|
||||
"country": "US",
|
||||
}
|
||||
|
||||
reqBody, _ = json.Marshal(profileReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/profiles", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w = httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
var profile map[string]interface{}
|
||||
err = json.Unmarshal(w.Body.Bytes(), &profile)
|
||||
require.NoError(suite.T(), err)
|
||||
profileID := fmt.Sprintf("%v", profile["id"])
|
||||
|
||||
// Activate
|
||||
req = httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/profiles/%s/activate", profileID), nil)
|
||||
w = httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
// Create rate plan
|
||||
rpReq := map[string]interface{}{
|
||||
"name": "Test Plan",
|
||||
"currency": "USD",
|
||||
"base_price": 10.0,
|
||||
}
|
||||
reqBody, _ = json.Marshal(rpReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/rateplans", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w = httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
var ratePlan map[string]interface{}
|
||||
err = json.Unmarshal(w.Body.Bytes(), &ratePlan)
|
||||
require.NoError(suite.T(), err)
|
||||
ratePlanID := fmt.Sprintf("%v", ratePlan["id"])
|
||||
|
||||
// Subscribe
|
||||
subReq = map[string]interface{}{
|
||||
"profile_id": profileID,
|
||||
"rate_plan_id": ratePlanID,
|
||||
"auto_renew": true,
|
||||
}
|
||||
reqBody, _ = json.Marshal(subReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/subscriptions", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w = httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
var subscription repository.RatePlanSubscription
|
||||
err = json.Unmarshal(w.Body.Bytes(), &subscription)
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
// Cancel subscription
|
||||
req = httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/subscriptions/%s/cancel", subscription.ID), nil)
|
||||
w = httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
// Try to bill cancelled subscription
|
||||
chargeReq := map[string]interface{}{
|
||||
"subscription_id": subscription.ID,
|
||||
"amount": 10.0,
|
||||
"currency": "USD",
|
||||
}
|
||||
reqBody, _ = json.Marshal(chargeReq)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/billing/charge", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusBadRequest, w.Code)
|
||||
}
|
||||
|
||||
// Handler implementations (simplified for testing)
|
||||
func (suite *SubscriberJourneyTestSuite) createProfile(c *gin.Context) {
|
||||
profile := map[string]interface{}{
|
||||
"id": fmt.Sprintf("profile-%d", time.Now().UnixNano()),
|
||||
"status": "pending",
|
||||
"created_at": time.Now(),
|
||||
}
|
||||
|
||||
// Add request fields
|
||||
var req map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range req {
|
||||
profile[k] = v
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, profile)
|
||||
}
|
||||
|
||||
func (suite *SubscriberJourneyTestSuite) getProfile(c *gin.Context) {
|
||||
profile := map[string]interface{}{
|
||||
"id": c.Param("id"),
|
||||
"status": "active",
|
||||
}
|
||||
c.JSON(http.StatusOK, profile)
|
||||
}
|
||||
|
||||
func (suite *SubscriberJourneyTestSuite) activateProfile(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"status": "activated"})
|
||||
}
|
||||
|
||||
func (suite *SubscriberJourneyTestSuite) createRatePlan(c *gin.Context) {
|
||||
ratePlan := map[string]interface{}{
|
||||
"id": fmt.Sprintf("plan-%d", time.Now().UnixNano()),
|
||||
"created_at": time.Now(),
|
||||
}
|
||||
|
||||
var req map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range req {
|
||||
ratePlan[k] = v
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, ratePlan)
|
||||
}
|
||||
|
||||
func (suite *SubscriberJourneyTestSuite) listRatePlans(c *gin.Context) {
|
||||
plans := []map[string]interface{}{
|
||||
{
|
||||
"id": "plan-1",
|
||||
"name": "Basic Plan",
|
||||
"price": 9.99,
|
||||
},
|
||||
{
|
||||
"id": "plan-2",
|
||||
"name": "Premium Plan",
|
||||
"price": 29.99,
|
||||
},
|
||||
}
|
||||
c.JSON(http.StatusOK, plans)
|
||||
}
|
||||
|
||||
func (suite *SubscriberJourneyTestSuite) getRatePlan(c *gin.Context) {
|
||||
ratePlan := map[string]interface{}{
|
||||
"id": c.Param("id"),
|
||||
"name": "Test Plan",
|
||||
}
|
||||
c.JSON(http.StatusOK, ratePlan)
|
||||
}
|
||||
|
||||
func (suite *SubscriberJourneyTestSuite) createSubscription(c *gin.Context) {
|
||||
var subscription repository.RatePlanSubscription
|
||||
if err := c.ShouldBindJSON(&subscription); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
subscription.ID = fmt.Sprintf("sub-%d", time.Now().UnixNano())
|
||||
subscription.Status = repository.SubscriptionStatus("active")
|
||||
subscription.StartedAt = time.Now()
|
||||
subscription.CreatedAt = time.Now()
|
||||
subscription.UpdatedAt = time.Now()
|
||||
subscription.CurrentCycle = time.Now()
|
||||
|
||||
c.JSON(http.StatusCreated, subscription)
|
||||
}
|
||||
|
||||
func (suite *SubscriberJourneyTestSuite) getSubscription(c *gin.Context) {
|
||||
var subscription repository.RatePlanSubscription
|
||||
if err := suite.db.First(&subscription, "id = ?", c.Param("id")).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "subscription not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, subscription)
|
||||
}
|
||||
|
||||
func (suite *SubscriberJourneyTestSuite) cancelSubscription(c *gin.Context) {
|
||||
if err := suite.db.Model(&repository.RatePlanSubscription{}).Where("id = ?", c.Param("id")).Updates(map[string]interface{}{
|
||||
"status": "cancelled",
|
||||
"ended_at": time.Now(),
|
||||
"updated_at": time.Now(),
|
||||
}).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"status": "cancelled"})
|
||||
}
|
||||
|
||||
func (suite *SubscriberJourneyTestSuite) recordUsage(c *gin.Context) {
|
||||
var usage repository.RatePlanUsage
|
||||
if err := c.ShouldBindJSON(&usage); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
usage.ID = fmt.Sprintf("usage-%d", time.Now().UnixNano())
|
||||
usage.RatePlanID = "test-plan"
|
||||
usage.LastUpdated = time.Now()
|
||||
|
||||
if err := suite.db.Create(&usage).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, usage)
|
||||
}
|
||||
|
||||
func (suite *SubscriberJourneyTestSuite) getUsage(c *gin.Context) {
|
||||
var usage []repository.RatePlanUsage
|
||||
if err := suite.db.Where("rate_plan_id = ?", "test-plan").Find(&usage).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, usage)
|
||||
}
|
||||
|
||||
func (suite *SubscriberJourneyTestSuite) processCharge(c *gin.Context) {
|
||||
var charge map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&charge); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
charge["id"] = fmt.Sprintf("charge-%d", time.Now().UnixNano())
|
||||
charge["status"] = "completed"
|
||||
charge["processed_at"] = time.Now()
|
||||
|
||||
c.JSON(http.StatusOK, charge)
|
||||
}
|
||||
|
||||
func (suite *SubscriberJourneyTestSuite) getInvoice(c *gin.Context) {
|
||||
invoice := map[string]interface{}{
|
||||
"subscription_id": c.Param("subscription_id"),
|
||||
"total_amount": 29.99,
|
||||
"currency": "USD",
|
||||
"status": "paid",
|
||||
"issued_at": time.Now(),
|
||||
}
|
||||
c.JSON(http.StatusOK, invoice)
|
||||
}
|
||||
|
||||
func (suite *SubscriberJourneyTestSuite) getDashboard(c *gin.Context) {
|
||||
dashboard := map[string]interface{}{
|
||||
"total_subscribers": int64(100),
|
||||
"active_subscriptions": int64(95),
|
||||
"monthly_revenue": 2999.0,
|
||||
"churn_rate": 0.05,
|
||||
"generated_at": time.Now(),
|
||||
}
|
||||
c.JSON(http.StatusOK, dashboard)
|
||||
}
|
||||
|
||||
func (suite *SubscriberJourneyTestSuite) getRevenue(c *gin.Context) {
|
||||
revenue := map[string]interface{}{
|
||||
"total_revenue": 50000.0,
|
||||
"monthly_revenue": 5000.0,
|
||||
"revenue_by_plan": map[string]float64{
|
||||
"basic": 1500.0,
|
||||
"premium": 3500.0,
|
||||
},
|
||||
"currency": "USD",
|
||||
}
|
||||
c.JSON(http.StatusOK, revenue)
|
||||
}
|
||||
|
||||
// TearDownSuite cleans up the test suite
|
||||
func (suite *SubscriberJourneyTestSuite) TearDownSuite() {
|
||||
sqlDB, err := suite.db.DB()
|
||||
if err == nil {
|
||||
sqlDB.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// TestSubscriberJourneySuite runs the E2E test suite
|
||||
func TestSubscriberJourneySuite(t *testing.T) {
|
||||
suite.Run(t, new(SubscriberJourneyTestSuite))
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// APIIntegrationTestSuite tests API integration
|
||||
type APIIntegrationTestSuite struct {
|
||||
suite.Suite
|
||||
db *gorm.DB
|
||||
router *gin.Engine
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
// SetupSuite sets up the test suite
|
||||
func (suite *APIIntegrationTestSuite) SetupSuite() {
|
||||
// Setup in-memory database for testing
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
// Auto-migrate tables
|
||||
err = db.AutoMigrate(
|
||||
&repository.RatePlanUsage{},
|
||||
&repository.RatePlanSubscription{},
|
||||
)
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
suite.db = db
|
||||
suite.logger = logrus.New()
|
||||
suite.logger.SetLevel(logrus.ErrorLevel)
|
||||
|
||||
// Setup router
|
||||
gin.SetMode(gin.TestMode)
|
||||
suite.router = gin.New()
|
||||
suite.setupRoutes()
|
||||
}
|
||||
|
||||
func (suite *APIIntegrationTestSuite) setupRoutes() {
|
||||
api := suite.router.Group("/api/v1")
|
||||
|
||||
// Rate plan usage routes
|
||||
usage := api.Group("/usage")
|
||||
{
|
||||
usage.POST("", suite.createUsage)
|
||||
usage.GET("/:id", suite.getUsage)
|
||||
usage.GET("", suite.listUsage)
|
||||
}
|
||||
|
||||
// Subscription routes
|
||||
subscriptions := api.Group("/subscriptions")
|
||||
{
|
||||
subscriptions.POST("", suite.createSubscription)
|
||||
subscriptions.GET("/:id", suite.getSubscription)
|
||||
subscriptions.PUT("/:id/cancel", suite.cancelSubscription)
|
||||
}
|
||||
|
||||
// Health check
|
||||
api.GET("/health", suite.healthCheck)
|
||||
}
|
||||
|
||||
// TestRatePlanUsageLifecycle tests usage tracking
|
||||
func (suite *APIIntegrationTestSuite) TestRatePlanUsageLifecycle() {
|
||||
// 1. Create a rate plan usage record
|
||||
usageReq := map[string]interface{}{
|
||||
"rate_plan_id": "plan-123",
|
||||
"profile_id": "profile-456",
|
||||
"cycle_start": "2026-01-01T00:00:00Z",
|
||||
"cycle_end": "2026-01-31T23:59:59Z",
|
||||
"data_used": int64(1024),
|
||||
"voice_used": int64(300),
|
||||
"sms_used": int64(50),
|
||||
}
|
||||
|
||||
reqBody, _ := json.Marshal(usageReq)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/usage", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusCreated, w.Code)
|
||||
|
||||
var usage repository.RatePlanUsage
|
||||
err := json.Unmarshal(w.Body.Bytes(), &usage)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), "plan-123", usage.RatePlanID)
|
||||
assert.Equal(suite.T(), "profile-456", usage.ProfileID)
|
||||
assert.Equal(suite.T(), int64(1024), usage.DataUsed)
|
||||
|
||||
usageID := usage.ID
|
||||
|
||||
// 2. Get the usage record
|
||||
req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/usage/%s", usageID), nil)
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
var retrievedUsage repository.RatePlanUsage
|
||||
err = json.Unmarshal(w.Body.Bytes(), &retrievedUsage)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), usageID, retrievedUsage.ID)
|
||||
|
||||
// 3. List usage records
|
||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/usage", nil)
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
var usageList []repository.RatePlanUsage
|
||||
err = json.Unmarshal(w.Body.Bytes(), &usageList)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Greater(suite.T(), len(usageList), 0)
|
||||
}
|
||||
|
||||
// TestSubscriptionLifecycle tests subscription management
|
||||
func (suite *APIIntegrationTestSuite) TestSubscriptionLifecycle() {
|
||||
// 1. Create a subscription
|
||||
subReq := map[string]interface{}{
|
||||
"profile_id": "profile-789",
|
||||
"rate_plan_id": "plan-abc",
|
||||
"status": "active",
|
||||
"billing_cycle": "monthly",
|
||||
"next_billing_date": "2026-02-01T00:00:00Z",
|
||||
"auto_renew": true,
|
||||
"applied_discounts": []string{"new-user-10"},
|
||||
}
|
||||
|
||||
reqBody, _ := json.Marshal(subReq)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/subscriptions", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusCreated, w.Code)
|
||||
|
||||
var subscription repository.RatePlanSubscription
|
||||
err := json.Unmarshal(w.Body.Bytes(), &subscription)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), "profile-789", subscription.ProfileID)
|
||||
assert.Equal(suite.T(), "plan-abc", subscription.RatePlanID)
|
||||
assert.Equal(suite.T(), repository.SubscriptionStatus("active"), subscription.Status)
|
||||
|
||||
subscriptionID := subscription.ID
|
||||
|
||||
// 2. Get the subscription
|
||||
req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/subscriptions/%s", subscriptionID), nil)
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
var retrievedSubscription repository.RatePlanSubscription
|
||||
err = json.Unmarshal(w.Body.Bytes(), &retrievedSubscription)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), subscriptionID, retrievedSubscription.ID)
|
||||
|
||||
// 3. Cancel the subscription
|
||||
req = httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/subscriptions/%s/cancel", subscriptionID), nil)
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
// 4. Verify subscription is cancelled
|
||||
req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/subscriptions/%s", subscriptionID), nil)
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
var cancelledSubscription repository.RatePlanSubscription
|
||||
err = json.Unmarshal(w.Body.Bytes(), &cancelledSubscription)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), repository.SubscriptionStatus("cancelled"), cancelledSubscription.Status)
|
||||
}
|
||||
|
||||
// TestHealthCheck tests the health endpoint
|
||||
func (suite *APIIntegrationTestSuite) TestHealthCheck() {
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/health", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
var health map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &health)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), "healthy", health["status"])
|
||||
assert.Contains(suite.T(), health, "timestamp")
|
||||
}
|
||||
|
||||
// TestErrorHandling tests API error handling
|
||||
func (suite *APIIntegrationTestSuite) TestErrorHandling() {
|
||||
// Test getting non-existent usage
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/usage/non-existent", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusNotFound, w.Code)
|
||||
|
||||
var errorResponse map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &errorResponse)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Contains(suite.T(), errorResponse, "error")
|
||||
|
||||
// Test invalid request body
|
||||
reqBody := []byte("{invalid json}")
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/usage", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusBadRequest, w.Code)
|
||||
}
|
||||
|
||||
// TestConcurrentRequests tests concurrent API requests
|
||||
func (suite *APIIntegrationTestSuite) TestConcurrentRequests() {
|
||||
// Create some test data first
|
||||
usage := repository.RatePlanUsage{
|
||||
RatePlanID: "test-plan",
|
||||
ProfileID: "test-profile",
|
||||
CycleStart: time.Now(),
|
||||
CycleEnd: time.Now().Add(30 * 24 * time.Hour),
|
||||
DataUsed: 100,
|
||||
VoiceUsed: 50,
|
||||
SMSUsed: 10,
|
||||
}
|
||||
err := suite.db.Create(&usage).Error
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
// Test concurrent requests
|
||||
const numGoroutines = 10
|
||||
const numRequests = 100
|
||||
|
||||
errChan := make(chan error, numGoroutines)
|
||||
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
go func() {
|
||||
for j := 0; j < numRequests/numGoroutines; j++ {
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/usage", nil)
|
||||
w := httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
errChan <- fmt.Errorf("unexpected status code: %d", w.Code)
|
||||
return
|
||||
}
|
||||
}
|
||||
errChan <- nil
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait for all goroutines to complete
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
err := <-errChan
|
||||
assert.NoError(suite.T(), err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRatePlanUsageValidation tests input validation
|
||||
func (suite *APIIntegrationTestSuite) TestRatePlanUsageValidation() {
|
||||
// Test missing required fields
|
||||
invalidReq := map[string]interface{}{
|
||||
"profile_id": "profile-123",
|
||||
// Missing rate_plan_id
|
||||
}
|
||||
|
||||
reqBody, _ := json.Marshal(invalidReq)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/usage", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusBadRequest, w.Code)
|
||||
|
||||
var errorResponse map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &errorResponse)
|
||||
require.NoError(suite.T(), err)
|
||||
assert.Contains(suite.T(), errorResponse, "error")
|
||||
|
||||
// Test invalid data types
|
||||
invalidReq2 := map[string]interface{}{
|
||||
"rate_plan_id": "plan-123",
|
||||
"profile_id": "profile-123",
|
||||
"data_used": "not-a-number", // Should be int64
|
||||
}
|
||||
|
||||
reqBody, _ = json.Marshal(invalidReq2)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/usage", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w = httptest.NewRecorder()
|
||||
|
||||
suite.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(suite.T(), http.StatusBadRequest, w.Code)
|
||||
}
|
||||
|
||||
// Handler implementations
|
||||
func (suite *APIIntegrationTestSuite) createUsage(c *gin.Context) {
|
||||
var usage repository.RatePlanUsage
|
||||
if err := c.ShouldBindJSON(&usage); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Generate ID if not provided
|
||||
if usage.ID == "" {
|
||||
usage.ID = fmt.Sprintf("usage-%d", time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// Set timestamps
|
||||
now := time.Now()
|
||||
usage.LastUpdated = now
|
||||
|
||||
if err := suite.db.Create(&usage).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, usage)
|
||||
}
|
||||
|
||||
func (suite *APIIntegrationTestSuite) getUsage(c *gin.Context) {
|
||||
var usage repository.RatePlanUsage
|
||||
if err := suite.db.First(&usage, "id = ?", c.Param("id")).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "usage record not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, usage)
|
||||
}
|
||||
|
||||
func (suite *APIIntegrationTestSuite) listUsage(c *gin.Context) {
|
||||
var usage []repository.RatePlanUsage
|
||||
if err := suite.db.Find(&usage).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, usage)
|
||||
}
|
||||
|
||||
func (suite *APIIntegrationTestSuite) createSubscription(c *gin.Context) {
|
||||
var subscription repository.RatePlanSubscription
|
||||
if err := c.ShouldBindJSON(&subscription); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Generate ID if not provided
|
||||
if subscription.ID == "" {
|
||||
subscription.ID = fmt.Sprintf("sub-%d", time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// Set timestamps
|
||||
now := time.Now()
|
||||
subscription.StartedAt = now
|
||||
subscription.CreatedAt = now
|
||||
subscription.UpdatedAt = now
|
||||
subscription.CurrentCycle = now
|
||||
|
||||
if err := suite.db.Create(&subscription).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, subscription)
|
||||
}
|
||||
|
||||
func (suite *APIIntegrationTestSuite) getSubscription(c *gin.Context) {
|
||||
var subscription repository.RatePlanSubscription
|
||||
if err := suite.db.First(&subscription, "id = ?", c.Param("id")).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "subscription not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, subscription)
|
||||
}
|
||||
|
||||
func (suite *APIIntegrationTestSuite) cancelSubscription(c *gin.Context) {
|
||||
if err := suite.db.Model(&repository.RatePlanSubscription{}).Where("id = ?", c.Param("id")).Updates(map[string]interface{}{
|
||||
"status": "cancelled",
|
||||
"ended_at": time.Now(),
|
||||
"updated_at": time.Now(),
|
||||
}).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"status": "cancelled"})
|
||||
}
|
||||
|
||||
func (suite *APIIntegrationTestSuite) healthCheck(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "healthy",
|
||||
"timestamp": time.Now(),
|
||||
"version": "1.0.0",
|
||||
})
|
||||
}
|
||||
|
||||
// TearDownSuite cleans up the test suite
|
||||
func (suite *APIIntegrationTestSuite) TearDownSuite() {
|
||||
sqlDB, err := suite.db.DB()
|
||||
if err == nil {
|
||||
sqlDB.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// TestAPIIntegrationSuite runs the integration test suite
|
||||
func TestAPIIntegrationSuite(t *testing.T) {
|
||||
suite.Run(t, new(APIIntegrationTestSuite))
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package integration
|
||||
|
||||
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/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/nutcas3/telecom-platform/apps/carrier-connector/internal/repository"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CarrierAPITestSuite tests carrier API integration
|
||||
type CarrierAPITestSuite struct {
|
||||
suite.Suite
|
||||
db *gorm.DB
|
||||
router *gin.Engine
|
||||
}
|
||||
|
||||
func (suite *CarrierAPITestSuite) SetupSuite() {
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
err = db.AutoMigrate(&repository.RatePlan{}, &repository.RatePlanSubscription{}, &repository.RatePlanUsage{})
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
suite.db = db
|
||||
gin.SetMode(gin.TestMode)
|
||||
suite.router = gin.New()
|
||||
suite.setupRoutes()
|
||||
}
|
||||
|
||||
func (suite *CarrierAPITestSuite) setupRoutes() {
|
||||
api := suite.router.Group("/api/v1")
|
||||
api.POST("/rateplans", suite.createRatePlan)
|
||||
api.GET("/rateplans", suite.listRatePlans)
|
||||
api.GET("/rateplans/:id", suite.getRatePlan)
|
||||
api.POST("/subscriptions", suite.createSubscription)
|
||||
api.GET("/subscriptions/:id", suite.getSubscription)
|
||||
api.POST("/usage", suite.recordUsage)
|
||||
api.GET("/health", suite.healthCheck)
|
||||
}
|
||||
|
||||
func (suite *CarrierAPITestSuite) TestRatePlanLifecycle() {
|
||||
plan := map[string]interface{}{
|
||||
"name": "Test Plan", "description": "Test", "carrier_id": "carrier-1",
|
||||
"region": "US", "base_price": 10.0, "currency": "USD", "is_active": true,
|
||||
}
|
||||
body, _ := json.Marshal(plan)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/rateplans", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
assert.Equal(suite.T(), http.StatusCreated, w.Code)
|
||||
|
||||
var created repository.RatePlan
|
||||
json.Unmarshal(w.Body.Bytes(), &created)
|
||||
assert.Equal(suite.T(), "Test Plan", created.Name)
|
||||
|
||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/rateplans/"+created.ID, nil)
|
||||
w = httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
|
||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/rateplans", nil)
|
||||
w = httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
func (suite *CarrierAPITestSuite) TestSubscriptionLifecycle() {
|
||||
plan := &repository.RatePlan{ID: "plan-1", Name: "Plan", CarrierID: "c1", BasePrice: 10, Currency: "USD", IsActive: true}
|
||||
suite.db.Create(plan)
|
||||
|
||||
sub := map[string]interface{}{"profile_id": "profile-1", "rate_plan_id": "plan-1", "auto_renew": true}
|
||||
body, _ := json.Marshal(sub)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/subscriptions", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
assert.Equal(suite.T(), http.StatusCreated, w.Code)
|
||||
|
||||
var created repository.RatePlanSubscription
|
||||
json.Unmarshal(w.Body.Bytes(), &created)
|
||||
assert.Equal(suite.T(), "profile-1", created.ProfileID)
|
||||
}
|
||||
|
||||
func (suite *CarrierAPITestSuite) TestUsageRecording() {
|
||||
usage := map[string]interface{}{"profile_id": "p1", "rate_plan_id": "rp1", "data_used": 100, "voice_used": 10, "sms_used": 5}
|
||||
body, _ := json.Marshal(usage)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/usage", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
assert.Equal(suite.T(), http.StatusCreated, w.Code)
|
||||
}
|
||||
|
||||
func (suite *CarrierAPITestSuite) TestHealthCheck() {
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/health", nil)
|
||||
w := httptest.NewRecorder()
|
||||
suite.router.ServeHTTP(w, req)
|
||||
assert.Equal(suite.T(), http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
func (suite *CarrierAPITestSuite) createRatePlan(c *gin.Context) {
|
||||
var plan repository.RatePlan
|
||||
if err := c.ShouldBindJSON(&plan); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
plan.ID = "rp-" + time.Now().Format("20060102150405")
|
||||
plan.CreatedAt = time.Now()
|
||||
plan.UpdatedAt = time.Now()
|
||||
suite.db.Create(&plan)
|
||||
c.JSON(http.StatusCreated, plan)
|
||||
}
|
||||
|
||||
func (suite *CarrierAPITestSuite) listRatePlans(c *gin.Context) {
|
||||
var plans []repository.RatePlan
|
||||
suite.db.Find(&plans)
|
||||
c.JSON(http.StatusOK, plans)
|
||||
}
|
||||
|
||||
func (suite *CarrierAPITestSuite) getRatePlan(c *gin.Context) {
|
||||
var plan repository.RatePlan
|
||||
if err := suite.db.First(&plan, "id = ?", c.Param("id")).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, plan)
|
||||
}
|
||||
|
||||
func (suite *CarrierAPITestSuite) createSubscription(c *gin.Context) {
|
||||
var sub repository.RatePlanSubscription
|
||||
if err := c.ShouldBindJSON(&sub); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
sub.ID = "sub-" + time.Now().Format("20060102150405")
|
||||
sub.Status = repository.SubscriptionStatusActive
|
||||
sub.StartedAt = time.Now()
|
||||
sub.CreatedAt = time.Now()
|
||||
sub.UpdatedAt = time.Now()
|
||||
suite.db.Create(&sub)
|
||||
c.JSON(http.StatusCreated, sub)
|
||||
}
|
||||
|
||||
func (suite *CarrierAPITestSuite) getSubscription(c *gin.Context) {
|
||||
var sub repository.RatePlanSubscription
|
||||
if err := suite.db.First(&sub, "id = ?", c.Param("id")).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, sub)
|
||||
}
|
||||
|
||||
func (suite *CarrierAPITestSuite) recordUsage(c *gin.Context) {
|
||||
var usage repository.RatePlanUsage
|
||||
if err := c.ShouldBindJSON(&usage); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
usage.ID = "usage-" + time.Now().Format("20060102150405")
|
||||
usage.LastUpdated = time.Now()
|
||||
suite.db.Create(&usage)
|
||||
c.JSON(http.StatusCreated, usage)
|
||||
}
|
||||
|
||||
func (suite *CarrierAPITestSuite) healthCheck(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"status": "healthy", "timestamp": time.Now()})
|
||||
}
|
||||
|
||||
func (suite *CarrierAPITestSuite) TearDownSuite() {
|
||||
if db, err := suite.db.DB(); err == nil {
|
||||
db.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCarrierAPITestSuite(t *testing.T) {
|
||||
suite.Run(t, new(CarrierAPITestSuite))
|
||||
}
|
||||
Reference in New Issue
Block a user