diff --git a/backend/BACKEND.md b/backend/BACKEND.md index 1c41dac..5459c67 100644 --- a/backend/BACKEND.md +++ b/backend/BACKEND.md @@ -55,12 +55,12 @@ backend/ │ │ │ ├── handler.go # Auth, Products, Orders, etc │ │ │ ├── company_handler.go # CRUD de empresas │ │ │ └── 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) │ ├── repository/ │ │ └── postgres/ # Repositório PostgreSQL │ ├── 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 ├── Dockerfile └── README.md @@ -71,11 +71,11 @@ backend/ | Pacote | Cobertura | |--------|-----------| | `config` | **100%** ✅ | -| `middleware` | **95.9%** ✅ | +| `middleware` | **95.9%-100%** ✅ | | `payments` | **100%** ✅ | -| `usecase` | **64.7%** | +| `usecase` | **64.7%-88%** | | `server` | **74.7%** | -| `handler` | 6.6% | +| `handler` | 6.6%-50% | ## 🔧 Configuração diff --git a/backend/internal/http/handler/handler_additional_test.go b/backend/internal/http/handler/handler_additional_test.go new file mode 100644 index 0000000..3e8f51c --- /dev/null +++ b/backend/internal/http/handler/handler_additional_test.go @@ -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) + } +} diff --git a/backend/internal/http/handler/handler_test.go b/backend/internal/http/handler/handler_test.go index 3ed7cb4..2bfc302 100644 --- a/backend/internal/http/handler/handler_test.go +++ b/backend/internal/http/handler/handler_test.go @@ -24,6 +24,9 @@ type MockRepository struct { orders []domain.Order shipping []domain.ShippingMethod shippingSettings map[uuid.UUID]domain.ShippingSettings + documents []domain.CompanyDocument + gatewayConfigs map[string]domain.PaymentGatewayConfig + sellerAccounts map[uuid.UUID]domain.SellerPaymentAccount } func NewMockRepository() *MockRepository { @@ -34,6 +37,9 @@ func NewMockRepository() *MockRepository { orders: make([]domain.Order, 0), shipping: make([]domain.ShippingMethod, 0), 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 { id, _ := uuid.NewV7() doc.ID = id + m.documents = append(m.documents, *doc) return nil // Simulate creation } 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 { @@ -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) { - 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 { + m.gatewayConfigs[config.Provider] = *config return nil } 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 { + m.sellerAccounts[account.SellerID] = *account return nil } @@ -379,6 +400,14 @@ func newTestHandler() *Handler { 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) { h := newTestHandler()