Merge pull request #56 from rede5/codex/aumentar-cobertura-de-testes-em-40%
test: expand backend coverage for notifications and credit line
This commit is contained in:
commit
80225e814f
5 changed files with 335 additions and 0 deletions
114
backend/internal/notifications/fcm_test.go
Normal file
114
backend/internal/notifications/fcm_test.go
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
package notifications
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
"github.com/saveinmed/backend-go/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type roundTripperFunc func(*http.Request) (*http.Response, error)
|
||||||
|
|
||||||
|
func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
return f(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterAndUnregisterToken(t *testing.T) {
|
||||||
|
svc := NewFCMService("")
|
||||||
|
ctx := context.Background()
|
||||||
|
userID := uuid.Must(uuid.NewV7())
|
||||||
|
|
||||||
|
if err := svc.RegisterToken(ctx, userID, ""); err == nil {
|
||||||
|
t.Fatal("expected error for empty token")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := svc.RegisterToken(ctx, userID, "token-1"); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := svc.RegisterToken(ctx, userID, "token-1"); err != nil {
|
||||||
|
t.Fatalf("unexpected error on duplicate token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(svc.tokens[userID]) != 1 {
|
||||||
|
t.Fatalf("expected 1 token, got %d", len(svc.tokens[userID]))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := svc.UnregisterToken(ctx, userID, "token-1"); err != nil {
|
||||||
|
t.Fatalf("unexpected error unregistering: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(svc.tokens[userID]) != 0 {
|
||||||
|
t.Fatalf("expected no tokens after unregister, got %d", len(svc.tokens[userID]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendPushSkipsWhenNoTokens(t *testing.T) {
|
||||||
|
svc := NewFCMService("")
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
if err := svc.SendPush(ctx, uuid.Must(uuid.NewV7()), "title", "body", nil); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendToFCMWithServerKey(t *testing.T) {
|
||||||
|
svc := NewFCMService("server-key")
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
var capturedAuth string
|
||||||
|
svc.httpClient = &http.Client{
|
||||||
|
Transport: roundTripperFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
|
capturedAuth = req.Header.Get("Authorization")
|
||||||
|
return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewBufferString("ok"))}, nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := svc.sendToFCM(ctx, FCMMessage{
|
||||||
|
To: "token",
|
||||||
|
Notification: &FCMNotification{
|
||||||
|
Title: "Hello",
|
||||||
|
Body: "World",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if capturedAuth != "key=server-key" {
|
||||||
|
t.Fatalf("expected auth header to include server key, got %q", capturedAuth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendToFCMRejectsNonOK(t *testing.T) {
|
||||||
|
svc := NewFCMService("server-key")
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
svc.httpClient = &http.Client{
|
||||||
|
Transport: roundTripperFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
|
return &http.Response{StatusCode: http.StatusBadRequest, Body: io.NopCloser(bytes.NewBufferString("bad"))}, nil
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := svc.sendToFCM(ctx, FCMMessage{To: "token"}); err == nil {
|
||||||
|
t.Fatal("expected error for non-OK response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotifyOrderStatusChangedUsesDefaultEmoji(t *testing.T) {
|
||||||
|
svc := NewFCMService("")
|
||||||
|
ctx := context.Background()
|
||||||
|
buyerID := uuid.Must(uuid.NewV7())
|
||||||
|
|
||||||
|
_ = svc.RegisterToken(ctx, buyerID, "token-1")
|
||||||
|
|
||||||
|
order := &domain.Order{ID: uuid.Must(uuid.NewV7()), Status: domain.OrderStatus("Em análise")}
|
||||||
|
buyer := &domain.User{ID: buyerID}
|
||||||
|
|
||||||
|
if err := svc.NotifyOrderStatusChanged(ctx, order, buyer); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
40
backend/internal/notifications/service_test.go
Normal file
40
backend/internal/notifications/service_test.go
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
package notifications
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
"github.com/saveinmed/backend-go/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoggerNotificationService(t *testing.T) {
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
original := log.Writer()
|
||||||
|
log.SetOutput(buffer)
|
||||||
|
defer log.SetOutput(original)
|
||||||
|
|
||||||
|
svc := NewLoggerNotificationService()
|
||||||
|
ctx := context.Background()
|
||||||
|
order := &domain.Order{ID: uuid.Must(uuid.NewV7()), TotalCents: 12345, Status: domain.OrderStatusPaid}
|
||||||
|
buyer := &domain.User{Email: "buyer@example.com", Name: "Buyer"}
|
||||||
|
seller := &domain.User{Email: "seller@example.com", Name: "Seller"}
|
||||||
|
|
||||||
|
if err := svc.NotifyOrderCreated(ctx, order, buyer, seller); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if err := svc.NotifyOrderStatusChanged(ctx, order, buyer); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := buffer.String()
|
||||||
|
if !strings.Contains(output, "Novo Pedido") {
|
||||||
|
t.Fatalf("expected output to include order created log, got %q", output)
|
||||||
|
}
|
||||||
|
if !strings.Contains(output, "Atualização do Pedido") {
|
||||||
|
t.Fatalf("expected output to include status change log, got %q", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
110
backend/internal/usecase/credit_line_test.go
Normal file
110
backend/internal/usecase/credit_line_test.go
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
"github.com/saveinmed/backend-go/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCheckCreditLineErrors(t *testing.T) {
|
||||||
|
svc, _ := newTestService()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
_, err := svc.CheckCreditLine(ctx, uuid.Must(uuid.NewV7()), 100)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for missing company")
|
||||||
|
}
|
||||||
|
|
||||||
|
company := domain.Company{ID: uuid.Must(uuid.NewV7())}
|
||||||
|
svc.repo.(*MockRepository).companies = append(svc.repo.(*MockRepository).companies, company)
|
||||||
|
|
||||||
|
_, err = svc.CheckCreditLine(ctx, company.ID, 100)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error when credit line not enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckCreditLineAvailable(t *testing.T) {
|
||||||
|
svc, repo := newTestService()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
company := domain.Company{
|
||||||
|
ID: uuid.Must(uuid.NewV7()),
|
||||||
|
CreditLimitCents: 10000,
|
||||||
|
CreditUsedCents: 2500,
|
||||||
|
}
|
||||||
|
repo.companies = append(repo.companies, company)
|
||||||
|
|
||||||
|
ok, err := svc.CheckCreditLine(ctx, company.ID, 5000)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected credit line to be available")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUseAndReleaseCreditLine(t *testing.T) {
|
||||||
|
svc, repo := newTestService()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
company := domain.Company{
|
||||||
|
ID: uuid.Must(uuid.NewV7()),
|
||||||
|
CreditLimitCents: 9000,
|
||||||
|
CreditUsedCents: 1000,
|
||||||
|
}
|
||||||
|
repo.companies = append(repo.companies, company)
|
||||||
|
|
||||||
|
if err := svc.UseCreditLine(ctx, company.ID, 3000); err != nil {
|
||||||
|
t.Fatalf("unexpected error using credit line: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, _ := repo.GetCompany(ctx, company.ID)
|
||||||
|
if updated.CreditUsedCents != 4000 {
|
||||||
|
t.Fatalf("expected credit used to be 4000, got %d", updated.CreditUsedCents)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := svc.ReleaseCreditLine(ctx, company.ID, 5000); err != nil {
|
||||||
|
t.Fatalf("unexpected error releasing credit: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, _ = repo.GetCompany(ctx, company.ID)
|
||||||
|
if updated.CreditUsedCents != 0 {
|
||||||
|
t.Fatalf("expected credit used to floor at 0, got %d", updated.CreditUsedCents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUseCreditLineInsufficient(t *testing.T) {
|
||||||
|
svc, repo := newTestService()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
company := domain.Company{
|
||||||
|
ID: uuid.Must(uuid.NewV7()),
|
||||||
|
CreditLimitCents: 5000,
|
||||||
|
CreditUsedCents: 4500,
|
||||||
|
}
|
||||||
|
repo.companies = append(repo.companies, company)
|
||||||
|
|
||||||
|
if err := svc.UseCreditLine(ctx, company.ID, 1000); err != ErrInsufficientCredit {
|
||||||
|
t.Fatalf("expected ErrInsufficientCredit, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetCreditLimit(t *testing.T) {
|
||||||
|
svc, repo := newTestService()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
company := domain.Company{ID: uuid.Must(uuid.NewV7())}
|
||||||
|
repo.companies = append(repo.companies, company)
|
||||||
|
|
||||||
|
if err := svc.SetCreditLimit(ctx, company.ID, 12000); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, _ := repo.GetCompany(ctx, company.ID)
|
||||||
|
if updated.CreditLimitCents != 12000 {
|
||||||
|
t.Fatalf("expected credit limit to be 12000, got %d", updated.CreditLimitCents)
|
||||||
|
}
|
||||||
|
}
|
||||||
53
backend/internal/usecase/payment_config_test.go
Normal file
53
backend/internal/usecase/payment_config_test.go
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
package usecase
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
"github.com/saveinmed/backend-go/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUpsertAndGetPaymentGatewayConfig(t *testing.T) {
|
||||||
|
svc, _ := newTestService()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
config := &domain.PaymentGatewayConfig{
|
||||||
|
Provider: "stripe",
|
||||||
|
Active: true,
|
||||||
|
Credentials: "{\"secret\":\"secret\"}",
|
||||||
|
Environment: "sandbox",
|
||||||
|
Commission: 2.9,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := svc.UpsertPaymentGatewayConfig(ctx, config); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stored, err := svc.GetPaymentGatewayConfig(ctx, "stripe")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if stored == nil || stored.Provider != "stripe" {
|
||||||
|
t.Fatal("expected stored config to be returned")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOnboardSellerStoresAccount(t *testing.T) {
|
||||||
|
svc, repo := newTestService()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
sellerID := uuid.Must(uuid.NewV7())
|
||||||
|
link, err := svc.OnboardSeller(ctx, sellerID, "stripe")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if link == "" {
|
||||||
|
t.Fatal("expected onboarding link")
|
||||||
|
}
|
||||||
|
|
||||||
|
stored, _ := repo.GetSellerPaymentAccount(ctx, sellerID)
|
||||||
|
if stored == nil || stored.Status != "pending" {
|
||||||
|
t.Fatal("expected seller payment account to be stored as pending")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,8 @@ type MockRepository struct {
|
||||||
reviews []domain.Review
|
reviews []domain.Review
|
||||||
shipping []domain.ShippingMethod
|
shipping []domain.ShippingMethod
|
||||||
shippingSettings map[uuid.UUID]domain.ShippingSettings
|
shippingSettings map[uuid.UUID]domain.ShippingSettings
|
||||||
|
paymentConfigs map[string]domain.PaymentGatewayConfig
|
||||||
|
sellerAccounts map[uuid.UUID]domain.SellerPaymentAccount
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMockRepository() *MockRepository {
|
func NewMockRepository() *MockRepository {
|
||||||
|
|
@ -31,6 +33,8 @@ func NewMockRepository() *MockRepository {
|
||||||
reviews: make([]domain.Review, 0),
|
reviews: make([]domain.Review, 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),
|
||||||
|
paymentConfigs: make(map[string]domain.PaymentGatewayConfig),
|
||||||
|
sellerAccounts: make(map[uuid.UUID]domain.SellerPaymentAccount),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -355,18 +359,32 @@ func (m *MockRepository) ListWithdrawals(ctx context.Context, companyID uuid.UUI
|
||||||
|
|
||||||
// Payment Config methods
|
// Payment Config methods
|
||||||
func (m *MockRepository) GetPaymentGatewayConfig(ctx context.Context, provider string) (*domain.PaymentGatewayConfig, error) {
|
func (m *MockRepository) GetPaymentGatewayConfig(ctx context.Context, provider string) (*domain.PaymentGatewayConfig, error) {
|
||||||
|
if config, ok := m.paymentConfigs[provider]; ok {
|
||||||
|
copied := config
|
||||||
|
return &copied, nil
|
||||||
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockRepository) UpsertPaymentGatewayConfig(ctx context.Context, config *domain.PaymentGatewayConfig) error {
|
func (m *MockRepository) UpsertPaymentGatewayConfig(ctx context.Context, config *domain.PaymentGatewayConfig) error {
|
||||||
|
if config != nil {
|
||||||
|
m.paymentConfigs[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) {
|
||||||
|
if account, ok := m.sellerAccounts[sellerID]; ok {
|
||||||
|
copied := account
|
||||||
|
return &copied, nil
|
||||||
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockRepository) UpsertSellerPaymentAccount(ctx context.Context, account *domain.SellerPaymentAccount) error {
|
func (m *MockRepository) UpsertSellerPaymentAccount(ctx context.Context, account *domain.SellerPaymentAccount) error {
|
||||||
|
if account != nil {
|
||||||
|
m.sellerAccounts[account.SellerID] = *account
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue