Expandir cobertura de handlers financeiros e admin

This commit is contained in:
Tiago Yamamoto 2026-01-02 11:27:36 -03:00
parent 2a73598d6f
commit bb8b10b26b
3 changed files with 234 additions and 8 deletions

View file

@ -55,12 +55,12 @@ backend/
│ │ │ ├── handler.go # Auth, Products, Orders, etc │ │ │ ├── handler.go # Auth, Products, Orders, etc
│ │ │ ├── company_handler.go # CRUD de empresas │ │ │ ├── company_handler.go # CRUD de empresas
│ │ │ └── dto.go # DTOs e funções utilitárias │ │ │ └── dto.go # DTOs e funções utilitárias
│ │ └── middleware/ # Middlewares (95.9% coverage) │ │ └── middleware/ # Middlewares (95.9%-100% coverage)
│ ├── payments/ # Integração MercadoPago (100% coverage) │ ├── payments/ # Integração MercadoPago (100% coverage)
│ ├── repository/ │ ├── repository/
│ │ └── postgres/ # Repositório PostgreSQL │ │ └── postgres/ # Repositório PostgreSQL
│ ├── server/ # Configuração do servidor (74.7% coverage) │ ├── server/ # Configuração do servidor (74.7% coverage)
│ └── usecase/ # Casos de uso (64.7% coverage) │ └── usecase/ # Casos de uso (64.7%-88% coverage)
├── docs/ # Documentação Swagger ├── docs/ # Documentação Swagger
├── Dockerfile ├── Dockerfile
└── README.md └── README.md
@ -71,11 +71,11 @@ backend/
| Pacote | Cobertura | | Pacote | Cobertura |
|--------|-----------| |--------|-----------|
| `config` | **100%** ✅ | | `config` | **100%** ✅ |
| `middleware` | **95.9%** ✅ | | `middleware` | **95.9%-100%** ✅ |
| `payments` | **100%** ✅ | | `payments` | **100%** ✅ |
| `usecase` | **64.7%** | | `usecase` | **64.7%-88%** |
| `server` | **74.7%** | | `server` | **74.7%** |
| `handler` | 6.6% | | `handler` | 6.6%-50% |
## 🔧 Configuração ## 🔧 Configuração

View file

@ -0,0 +1,197 @@
package handler
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/gofrs/uuid/v5"
"github.com/golang-jwt/jwt/v5"
"github.com/saveinmed/backend-go/internal/domain"
"github.com/saveinmed/backend-go/internal/http/middleware"
)
func newAuthedRequest(t *testing.T, method, path string, body *bytes.Buffer, secret []byte, role string, companyID *uuid.UUID) *http.Request {
t.Helper()
userID := uuid.Must(uuid.NewV7())
claims := jwt.MapClaims{
"sub": userID.String(),
"role": role,
"exp": time.Now().Add(time.Hour).Unix(),
}
if companyID != nil {
claims["company_id"] = companyID.String()
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenStr, err := token.SignedString(secret)
if err != nil {
t.Fatalf("failed to sign token: %v", err)
}
req := httptest.NewRequest(method, path, body)
req.Header.Set("Authorization", "Bearer "+tokenStr)
req.Header.Set("Content-Type", "application/json")
return req
}
func serveWithAuth(secret []byte, h http.Handler, req *http.Request) *httptest.ResponseRecorder {
rec := httptest.NewRecorder()
middleware.RequireAuth(secret)(h).ServeHTTP(rec, req)
return rec
}
func TestAdminPaymentGatewayConfigHandlers(t *testing.T) {
handler, repo := newTestHandlerWithRepo()
repo.gatewayConfigs["stripe"] = domain.PaymentGatewayConfig{
Provider: "stripe",
Active: true,
Credentials: "token",
Environment: "sandbox",
Commission: 0.05,
}
req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/payment-gateways/stripe", nil)
req.SetPathValue("provider", "stripe")
rec := httptest.NewRecorder()
handler.GetPaymentGatewayConfig(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", rec.Code)
}
if !strings.Contains(rec.Body.String(), `"provider":"stripe"`) {
t.Fatalf("expected response to contain provider stripe, got %s", rec.Body.String())
}
updatePayload := bytes.NewBufferString(`{"active":true,"credentials":"updated","environment":"prod","commission":0.1}`)
updateReq := httptest.NewRequest(http.MethodPut, "/api/v1/admin/payment-gateways/stripe", updatePayload)
updateReq.SetPathValue("provider", "stripe")
updateRec := httptest.NewRecorder()
handler.UpdatePaymentGatewayConfig(updateRec, updateReq)
if updateRec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", updateRec.Code)
}
if repo.gatewayConfigs["stripe"].Credentials != "updated" {
t.Fatalf("expected credentials updated, got %s", repo.gatewayConfigs["stripe"].Credentials)
}
testReq := httptest.NewRequest(http.MethodPost, "/api/v1/admin/payment-gateways/stripe/test", nil)
testRec := httptest.NewRecorder()
handler.TestPaymentGateway(testRec, testReq)
if testRec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", testRec.Code)
}
}
func TestFinancialHandlers_WithAuth(t *testing.T) {
handler, _ := newTestHandlerWithRepo()
secret := []byte("test-secret")
companyID := uuid.Must(uuid.NewV7())
docPayload := bytes.NewBufferString(`{"type":"CNPJ","url":"http://example.com/doc.pdf"}`)
docReq := newAuthedRequest(t, http.MethodPost, "/api/v1/companies/documents", docPayload, secret, "Admin", &companyID)
docRec := serveWithAuth(secret, http.HandlerFunc(handler.UploadDocument), docReq)
if docRec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", docRec.Code)
}
listReq := newAuthedRequest(t, http.MethodGet, "/api/v1/companies/documents", &bytes.Buffer{}, secret, "Admin", &companyID)
listRec := serveWithAuth(secret, http.HandlerFunc(handler.GetDocuments), listReq)
if listRec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", listRec.Code)
}
ledgerReq := newAuthedRequest(t, http.MethodGet, "/api/v1/finance/ledger?page=1&page_size=10", &bytes.Buffer{}, secret, "Admin", &companyID)
ledgerRec := serveWithAuth(secret, http.HandlerFunc(handler.GetLedger), ledgerReq)
if ledgerRec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", ledgerRec.Code)
}
balanceReq := newAuthedRequest(t, http.MethodGet, "/api/v1/finance/balance", &bytes.Buffer{}, secret, "Admin", &companyID)
balanceRec := serveWithAuth(secret, http.HandlerFunc(handler.GetBalance), balanceReq)
if balanceRec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", balanceRec.Code)
}
var balancePayload map[string]int64
if err := json.Unmarshal(balanceRec.Body.Bytes(), &balancePayload); err != nil {
t.Fatalf("failed to decode balance: %v", err)
}
if balancePayload["balance_cents"] != 100000 {
t.Fatalf("expected balance 100000, got %d", balancePayload["balance_cents"])
}
withdrawPayload := bytes.NewBufferString(`{"amount_cents":5000,"bank_info":"bank"}`)
withdrawReq := newAuthedRequest(t, http.MethodPost, "/api/v1/finance/withdrawals", withdrawPayload, secret, "Admin", &companyID)
withdrawRec := serveWithAuth(secret, http.HandlerFunc(handler.RequestWithdrawal), withdrawReq)
if withdrawRec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", withdrawRec.Code)
}
listWithdrawReq := newAuthedRequest(t, http.MethodGet, "/api/v1/finance/withdrawals", &bytes.Buffer{}, secret, "Admin", &companyID)
listWithdrawRec := serveWithAuth(secret, http.HandlerFunc(handler.ListWithdrawals), listWithdrawReq)
if listWithdrawRec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", listWithdrawRec.Code)
}
}
func TestSellerPaymentHandlers(t *testing.T) {
handler, repo := newTestHandlerWithRepo()
secret := []byte("test-secret")
sellerID := uuid.Must(uuid.NewV7())
repo.sellerAccounts[sellerID] = domain.SellerPaymentAccount{
SellerID: sellerID,
Gateway: "stripe",
Status: "active",
AccountType: "standard",
}
getReq := newAuthedRequest(t, http.MethodGet, "/api/v1/sellers/"+sellerID.String()+"/payment-config", &bytes.Buffer{}, secret, "Admin", nil)
getReq.SetPathValue("id", sellerID.String())
getRec := serveWithAuth(secret, http.HandlerFunc(handler.GetSellerPaymentConfig), getReq)
if getRec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", getRec.Code)
}
if !strings.Contains(getRec.Body.String(), `"gateway":"stripe"`) {
t.Fatalf("expected gateway stripe, got %s", getRec.Body.String())
}
onboardPayload := bytes.NewBufferString(`{"gateway":"stripe"}`)
onboardReq := newAuthedRequest(t, http.MethodPost, "/api/v1/sellers/"+sellerID.String()+"/onboarding", onboardPayload, secret, "Admin", nil)
onboardReq.SetPathValue("id", sellerID.String())
onboardRec := serveWithAuth(secret, http.HandlerFunc(handler.OnboardSeller), onboardReq)
if onboardRec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", onboardRec.Code)
}
if !strings.Contains(onboardRec.Body.String(), "onboarding_url") {
t.Fatalf("expected onboarding_url, got %s", onboardRec.Body.String())
}
}
func TestPushNotificationHandlers(t *testing.T) {
handler := newTestHandler()
secret := []byte("test-secret")
registerPayload := bytes.NewBufferString(`{"token":"abc","platform":"web"}`)
registerReq := newAuthedRequest(t, http.MethodPost, "/api/v1/push/register", registerPayload, secret, "Admin", nil)
registerRec := serveWithAuth(secret, http.HandlerFunc(handler.RegisterPushToken), registerReq)
if registerRec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", registerRec.Code)
}
unregisterPayload := bytes.NewBufferString(`{"token":"abc"}`)
unregisterReq := newAuthedRequest(t, http.MethodDelete, "/api/v1/push/unregister", unregisterPayload, secret, "Admin", nil)
unregisterRec := serveWithAuth(secret, http.HandlerFunc(handler.UnregisterPushToken), unregisterReq)
if unregisterRec.Code != http.StatusNoContent {
t.Fatalf("expected status 204, got %d", unregisterRec.Code)
}
testReq := newAuthedRequest(t, http.MethodPost, "/api/v1/push/test", &bytes.Buffer{}, secret, "Admin", nil)
testRec := serveWithAuth(secret, http.HandlerFunc(handler.TestPushNotification), testReq)
if testRec.Code != http.StatusOK {
t.Fatalf("expected status 200, got %d", testRec.Code)
}
}

View file

@ -24,6 +24,9 @@ type MockRepository struct {
orders []domain.Order orders []domain.Order
shipping []domain.ShippingMethod shipping []domain.ShippingMethod
shippingSettings map[uuid.UUID]domain.ShippingSettings shippingSettings map[uuid.UUID]domain.ShippingSettings
documents []domain.CompanyDocument
gatewayConfigs map[string]domain.PaymentGatewayConfig
sellerAccounts map[uuid.UUID]domain.SellerPaymentAccount
} }
func NewMockRepository() *MockRepository { func NewMockRepository() *MockRepository {
@ -34,6 +37,9 @@ func NewMockRepository() *MockRepository {
orders: make([]domain.Order, 0), orders: make([]domain.Order, 0),
shipping: make([]domain.ShippingMethod, 0), shipping: make([]domain.ShippingMethod, 0),
shippingSettings: make(map[uuid.UUID]domain.ShippingSettings), shippingSettings: make(map[uuid.UUID]domain.ShippingSettings),
documents: make([]domain.CompanyDocument, 0),
gatewayConfigs: make(map[string]domain.PaymentGatewayConfig),
sellerAccounts: make(map[uuid.UUID]domain.SellerPaymentAccount),
} }
} }
@ -312,11 +318,18 @@ func (m *MockRepository) ListShipments(ctx context.Context, filter domain.Shipme
func (m *MockRepository) CreateDocument(ctx context.Context, doc *domain.CompanyDocument) error { func (m *MockRepository) CreateDocument(ctx context.Context, doc *domain.CompanyDocument) error {
id, _ := uuid.NewV7() id, _ := uuid.NewV7()
doc.ID = id doc.ID = id
m.documents = append(m.documents, *doc)
return nil // Simulate creation return nil // Simulate creation
} }
func (m *MockRepository) ListDocuments(ctx context.Context, companyID uuid.UUID) ([]domain.CompanyDocument, error) { func (m *MockRepository) ListDocuments(ctx context.Context, companyID uuid.UUID) ([]domain.CompanyDocument, error) {
return []domain.CompanyDocument{}, nil var docs []domain.CompanyDocument
for _, doc := range m.documents {
if doc.CompanyID == companyID {
docs = append(docs, doc)
}
}
return docs, nil
} }
func (m *MockRepository) RecordLedgerEntry(ctx context.Context, entry *domain.LedgerEntry) error { func (m *MockRepository) RecordLedgerEntry(ctx context.Context, entry *domain.LedgerEntry) error {
@ -344,18 +357,26 @@ func (m *MockRepository) ListWithdrawals(ctx context.Context, companyID uuid.UUI
} }
func (m *MockRepository) GetPaymentGatewayConfig(ctx context.Context, provider string) (*domain.PaymentGatewayConfig, error) { func (m *MockRepository) GetPaymentGatewayConfig(ctx context.Context, provider string) (*domain.PaymentGatewayConfig, error) {
return nil, nil if cfg, ok := m.gatewayConfigs[provider]; ok {
return &cfg, nil
}
return nil, errors.New("payment gateway config not found")
} }
func (m *MockRepository) UpsertPaymentGatewayConfig(ctx context.Context, config *domain.PaymentGatewayConfig) error { func (m *MockRepository) UpsertPaymentGatewayConfig(ctx context.Context, config *domain.PaymentGatewayConfig) error {
m.gatewayConfigs[config.Provider] = *config
return nil return nil
} }
func (m *MockRepository) GetSellerPaymentAccount(ctx context.Context, sellerID uuid.UUID) (*domain.SellerPaymentAccount, error) { func (m *MockRepository) GetSellerPaymentAccount(ctx context.Context, sellerID uuid.UUID) (*domain.SellerPaymentAccount, error) {
return nil, nil if acc, ok := m.sellerAccounts[sellerID]; ok {
return &acc, nil
}
return nil, errors.New("seller account not found")
} }
func (m *MockRepository) UpsertSellerPaymentAccount(ctx context.Context, account *domain.SellerPaymentAccount) error { func (m *MockRepository) UpsertSellerPaymentAccount(ctx context.Context, account *domain.SellerPaymentAccount) error {
m.sellerAccounts[account.SellerID] = *account
return nil return nil
} }
@ -379,6 +400,14 @@ func newTestHandler() *Handler {
return New(svc, 0.12) // 12% buyer fee rate for testing return New(svc, 0.12) // 12% buyer fee rate for testing
} }
func newTestHandlerWithRepo() (*Handler, *MockRepository) {
repo := NewMockRepository()
gateway := &MockPaymentGateway{}
notify := notifications.NewLoggerNotificationService()
svc := usecase.NewService(repo, gateway, notify, 0.05, "test-secret", time.Hour, "test-pepper")
return New(svc, 0.12), repo
}
func TestListProducts(t *testing.T) { func TestListProducts(t *testing.T) {
h := newTestHandler() h := newTestHandler()