131 lines
5.5 KiB
Go
131 lines
5.5 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
"github.com/gochat/gochat/internal/repository"
|
|
"github.com/gochat/gochat/internal/worker"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func TestEnterpriseBillingWorkerCreatesBRLCustomerAndSubscription(t *testing.T) {
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=private", t.Name())), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&model.Account{}, &model.User{}, &model.AccountUser{}, &model.InstallationConfig{}, &model.BackgroundJob{}))
|
|
t.Cleanup(func() {
|
|
sqlDB, _ := db.DB()
|
|
_ = sqlDB.Close()
|
|
})
|
|
|
|
account := &model.Account{Name: "Acme Brasil", Locale: "pt_BR", Active: true, FeatureFlags: `{"sla":true}`}
|
|
admin := &model.User{Name: "Admin", Email: "admin@example.com", Password: "hashed", Active: true}
|
|
require.NoError(t, db.Create(account).Error)
|
|
require.NoError(t, db.Create(admin).Error)
|
|
require.NoError(t, db.Create(&model.AccountUser{AccountID: account.ID, UserID: admin.ID, Role: "administrator"}).Error)
|
|
require.NoError(t, db.Create(&model.InstallationConfig{Name: "ENABLE_MULTI_CURRENCY_BILLING", Value: "true"}).Error)
|
|
require.NoError(t, db.Create(&model.InstallationConfig{
|
|
Name: "CHATWOOT_CLOUD_PLANS",
|
|
Value: `[{"name":"Hacker","product_id":"prod_default","price_ids":{"usd":["price_usd"],"brl":["price_brl"]},"default_quantity":2}]`,
|
|
}).Error)
|
|
|
|
var mu sync.Mutex
|
|
requests := map[string]url.Values{}
|
|
stripe := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body, readErr := io.ReadAll(r.Body)
|
|
require.NoError(t, readErr)
|
|
values, parseErr := url.ParseQuery(string(body))
|
|
require.NoError(t, parseErr)
|
|
mu.Lock()
|
|
requests[r.URL.Path] = values
|
|
mu.Unlock()
|
|
w.Header().Set("Content-Type", "application/json")
|
|
switch r.URL.Path {
|
|
case "/v1/customers":
|
|
_, _ = w.Write([]byte(`{"id":"cus_test"}`))
|
|
case "/v1/subscriptions":
|
|
_, _ = w.Write([]byte(`{"status":"active","items":{"data":[{"quantity":2,"current_period_end":1780000000,"price":{"id":"price_brl","product":"prod_default","currency":"brl"}}]}}`))
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
}))
|
|
t.Cleanup(stripe.Close)
|
|
t.Setenv("STRIPE_SECRET_KEY", "sk_test")
|
|
t.Setenv("STRIPE_API_BASE", stripe.URL)
|
|
|
|
svc := NewAccountService(repository.NewAccountRepo(db))
|
|
wp := worker.NewWorkerPool(db)
|
|
svc.SetWorkerPool(wp)
|
|
require.NoError(t, svc.SelectBillingCurrency(context.Background(), admin.ID, account.ID, "brl"))
|
|
|
|
var job model.BackgroundJob
|
|
require.NoError(t, db.Where("job_type = ?", TaskTypeEnterpriseCreateStripeCustomer).First(&job).Error)
|
|
require.Equal(t, model.BackgroundJobStatusQueued, job.Status)
|
|
require.Equal(t, "default", job.Queue)
|
|
require.Equal(t, 3, job.MaxAttempts)
|
|
require.Equal(t, fmt.Sprintf("stripe-customer:%d", account.ID), job.IdempotencyKey)
|
|
|
|
processed, err := wp.ProcessOne(context.Background())
|
|
require.NoError(t, err)
|
|
require.True(t, processed)
|
|
require.NoError(t, db.First(account, account.ID).Error)
|
|
attrs := account.CustomAttributesMap()
|
|
require.NotContains(t, attrs, "is_creating_customer")
|
|
require.Equal(t, "cus_test", attrs["stripe_customer_id"])
|
|
require.Equal(t, "price_brl", attrs["stripe_price_id"])
|
|
require.Equal(t, "prod_default", attrs["stripe_product_id"])
|
|
require.Equal(t, "Hacker", attrs["plan_name"])
|
|
require.Equal(t, float64(2), attrs["subscribed_quantity"])
|
|
require.Equal(t, "active", attrs["subscription_status"])
|
|
require.Equal(t, "2026-05-28T20:26:40Z", attrs["subscription_ends_on"])
|
|
require.Equal(t, "brl", attrs["billing_currency"])
|
|
|
|
mu.Lock()
|
|
customerForm := requests["/v1/customers"]
|
|
subscriptionForm := requests["/v1/subscriptions"]
|
|
mu.Unlock()
|
|
require.Equal(t, "Acme Brasil", customerForm.Get("name"))
|
|
require.Equal(t, "admin@example.com", customerForm.Get("email"))
|
|
require.Equal(t, "BR", customerForm.Get("address[country]"))
|
|
require.Equal(t, "pt-BR", customerForm.Get("preferred_locales[0]"))
|
|
require.Equal(t, "cus_test", subscriptionForm.Get("customer"))
|
|
require.Equal(t, "price_brl", subscriptionForm.Get("items[0][price]"))
|
|
require.Equal(t, "2", subscriptionForm.Get("items[0][quantity]"))
|
|
}
|
|
|
|
func TestEnterpriseBillingWorkerAlwaysClearsCreationFlagOnFailure(t *testing.T) {
|
|
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("file:%s?mode=memory&cache=private", t.Name())), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&model.Account{}, &model.User{}, &model.AccountUser{}, &model.InstallationConfig{}, &model.BackgroundJob{}))
|
|
t.Cleanup(func() {
|
|
sqlDB, _ := db.DB()
|
|
_ = sqlDB.Close()
|
|
})
|
|
|
|
account := &model.Account{Name: "Acme", Locale: "en", Active: true}
|
|
admin := &model.User{Name: "Admin", Email: "admin@example.com", Password: "hashed", Active: true}
|
|
require.NoError(t, db.Create(account).Error)
|
|
require.NoError(t, db.Create(admin).Error)
|
|
require.NoError(t, db.Create(&model.AccountUser{AccountID: account.ID, UserID: admin.ID, Role: "administrator"}).Error)
|
|
|
|
svc := NewAccountService(repository.NewAccountRepo(db))
|
|
wp := worker.NewWorkerPool(db)
|
|
svc.SetWorkerPool(wp)
|
|
require.NoError(t, svc.EnsureEnterpriseAccountCustomerCreationFlag(context.Background(), account.ID, admin.ID))
|
|
|
|
processed, err := wp.ProcessOne(context.Background())
|
|
require.ErrorContains(t, err, "STRIPE_SECRET_KEY is not configured")
|
|
require.True(t, processed)
|
|
require.NoError(t, db.First(account, account.ID).Error)
|
|
require.NotContains(t, account.CustomAttributesMap(), "is_creating_customer")
|
|
}
|