diff --git a/backend/internal/repository/postgres/repository_test.go b/backend/internal/repository/postgres/repository_test.go index d37b1df..163b65c 100644 --- a/backend/internal/repository/postgres/repository_test.go +++ b/backend/internal/repository/postgres/repository_test.go @@ -28,18 +28,21 @@ func TestCreateCompany(t *testing.T) { defer repo.db.Close() company := &domain.Company{ - ID: uuid.Must(uuid.NewV7()), - CNPJ: "12345678901234", - CorporateName: "Test Pharmacy", - Category: "farmacia", - LicenseNumber: "123", - IsVerified: false, - Latitude: -10.0, - Longitude: -20.0, - City: "Test City", - State: "TS", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + ID: uuid.Must(uuid.NewV7()), + CNPJ: "12345678901234", + CorporateName: "Test Pharmacy", + Category: "farmacia", + LicenseNumber: "123", + IsVerified: false, + Latitude: -10.0, + Longitude: -20.0, + City: "Test City", + State: "TS", + Phone: "(11) 99999-9999", + OperatingHours: "08:00-18:00", + Is24Hours: false, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), } query := `INSERT INTO companies` @@ -55,6 +58,9 @@ func TestCreateCompany(t *testing.T) { company.Longitude, company.City, company.State, + company.Phone, + company.OperatingHours, + company.Is24Hours, sqlmock.AnyArg(), // CreatedAt sqlmock.AnyArg(), // UpdatedAt ). @@ -97,13 +103,19 @@ func TestCreateProduct(t *testing.T) { defer repo.db.Close() product := &domain.Product{ - ID: uuid.Must(uuid.NewV7()), - SellerID: uuid.Must(uuid.NewV7()), - Name: "Test Product", - Description: "Desc", - Batch: "B1", - PriceCents: 1000, - Stock: 10, + ID: uuid.Must(uuid.NewV7()), + SellerID: uuid.Must(uuid.NewV7()), + EANCode: "7891234567890", + Name: "Test Product", + Description: "Desc", + Manufacturer: "Test Manufacturer", + Category: "medicamento", + Subcategory: "analgésico", + Batch: "B1", + ExpiresAt: time.Now().AddDate(1, 0, 0), + PriceCents: 1000, + Stock: 10, + Observations: "Test observations", } query := `INSERT INTO products` @@ -113,12 +125,17 @@ func TestCreateProduct(t *testing.T) { WithArgs( product.ID, product.SellerID, + product.EANCode, product.Name, product.Description, + product.Manufacturer, + product.Category, + product.Subcategory, product.Batch, product.ExpiresAt, product.PriceCents, product.Stock, + product.Observations, ). WillReturnRows(rows) @@ -141,3 +158,126 @@ func TestListProducts(t *testing.T) { assert.Equal(t, int64(1), count) assert.Len(t, list, 1) } + +func TestCreateUser(t *testing.T) { + repo, mock := newMockRepo(t) + defer repo.db.Close() + + user := &domain.User{ + ID: uuid.Must(uuid.NewV7()), + CompanyID: uuid.Must(uuid.NewV7()), + Role: "Colaborador", + Name: "Test User", + Username: "testuser", + Email: "test@example.com", + PasswordHash: "hashed_password", + } + + query := `INSERT INTO users` + mock.ExpectExec(regexp.QuoteMeta(query)). + WithArgs( + user.ID, + user.CompanyID, + user.Role, + user.Name, + user.Username, + user.Email, + user.EmailVerified, // email_verified + user.PasswordHash, // password_hash + sqlmock.AnyArg(), // CreatedAt + sqlmock.AnyArg(), // UpdatedAt + ). + WillReturnResult(sqlmock.NewResult(1, 1)) + + err := repo.CreateUser(context.Background(), user) + assert.NoError(t, err) +} + +func TestListUsers(t *testing.T) { + repo, mock := newMockRepo(t) + defer repo.db.Close() + + companyID := uuid.Must(uuid.NewV7()) + userID := uuid.Must(uuid.NewV7()) + + rows := sqlmock.NewRows([]string{ + "id", "company_id", "role", "name", "username", "email", + "email_verified", "password_hash", "created_at", "updated_at", + }).AddRow( + userID, companyID, "Owner", "Test", "test", "test@test.com", + true, "hash", time.Now(), time.Now(), + ) + + mock.ExpectQuery(`SELECT count\(\*\) FROM users`). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1)) + mock.ExpectQuery(`SELECT .* FROM users`). + WithArgs(20, 0). + WillReturnRows(rows) + + list, count, err := repo.ListUsers(context.Background(), domain.UserFilter{Limit: 20}) + assert.NoError(t, err) + assert.Equal(t, int64(1), count) + assert.Len(t, list, 1) +} + +func TestListOrders(t *testing.T) { + repo, mock := newMockRepo(t) + defer repo.db.Close() + + orderID := uuid.Must(uuid.NewV7()) + buyerID := uuid.Must(uuid.NewV7()) + sellerID := uuid.Must(uuid.NewV7()) + + rows := sqlmock.NewRows([]string{ + "id", "buyer_id", "seller_id", "status", "total_cents", "payment_method", + "shipping_recipient_name", "shipping_street", "shipping_number", "shipping_complement", + "shipping_district", "shipping_city", "shipping_state", "shipping_zip_code", + "shipping_country", "created_at", "updated_at", + }).AddRow( + orderID, buyerID, sellerID, "Pendente", 10000, "pix", + "Test User", "Test Street", "123", "", "Centro", "City", "ST", "12345-678", "Brasil", + time.Now(), time.Now(), + ) + + mock.ExpectQuery(`SELECT count\(\*\) FROM orders`). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1)) + mock.ExpectQuery(`SELECT .* FROM orders`). + WithArgs(20, 0). + WillReturnRows(rows) + + // Expect query for order items + mock.ExpectQuery(`SELECT .* FROM order_items WHERE order_id`). + WithArgs(orderID). + WillReturnRows(sqlmock.NewRows([]string{"id", "order_id", "product_id", "quantity", "unit_cents", "batch", "expires_at"})) + + list, count, err := repo.ListOrders(context.Background(), domain.OrderFilter{Limit: 20}) + assert.NoError(t, err) + assert.Equal(t, int64(1), count) + assert.Len(t, list, 1) +} + +func TestGetShippingSettings(t *testing.T) { + repo, mock := newMockRepo(t) + defer repo.db.Close() + + vendorID := uuid.Must(uuid.NewV7()) + + rows := sqlmock.NewRows([]string{ + "vendor_id", "active", "max_radius_km", "price_per_km_cents", "min_fee_cents", + "free_shipping_threshold_cents", "pickup_active", "pickup_address", "pickup_hours", + "latitude", "longitude", "created_at", "updated_at", + }).AddRow( + vendorID, true, 50.0, 150, 1000, nil, true, "Rua Test, 123", "08:00-18:00", + -23.55, -46.63, time.Now(), time.Now(), + ) + + mock.ExpectQuery(`SELECT \* FROM shipping_settings WHERE vendor_id`). + WithArgs(vendorID). + WillReturnRows(rows) + + settings, err := repo.GetShippingSettings(context.Background(), vendorID) + assert.NoError(t, err) + assert.NotNil(t, settings) + assert.Equal(t, vendorID, settings.VendorID) + assert.True(t, settings.Active) +} diff --git a/marketplace/src/services/ordersService.test.ts b/marketplace/src/services/ordersService.test.ts new file mode 100644 index 0000000..64a92c7 --- /dev/null +++ b/marketplace/src/services/ordersService.test.ts @@ -0,0 +1,122 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { ordersService, CreateOrderRequest } from './ordersService' +import { apiClient } from './apiClient' + +// Mock apiClient +vi.mock('./apiClient', () => ({ + apiClient: { + get: vi.fn(), + post: vi.fn() + } +})) + +describe('ordersService', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('listOrders', () => { + it('should fetch orders list', async () => { + const mockOrders = { + orders: [ + { id: 'order-1', buyer_id: 'buyer-1', seller_id: 'seller-1', status: 'Pendente', total_cents: 10000 }, + { id: 'order-2', buyer_id: 'buyer-1', seller_id: 'seller-2', status: 'Pago', total_cents: 25000 } + ], + total: 2, + page: 1, + page_size: 20 + } + + vi.mocked(apiClient.get).mockResolvedValue(mockOrders) + + const result = await ordersService.listOrders() + + expect(apiClient.get).toHaveBeenCalledWith('/v1/orders') + expect(result).toEqual(mockOrders) + }) + + it('should handle empty orders list', async () => { + const mockOrders = { + orders: [], + total: 0, + page: 1, + page_size: 20 + } + + vi.mocked(apiClient.get).mockResolvedValue(mockOrders) + + const result = await ordersService.listOrders() + + expect(result).toEqual(mockOrders) + }) + + it('should handle API error', async () => { + vi.mocked(apiClient.get).mockRejectedValue(new Error('Internal Server Error')) + + await expect(ordersService.listOrders()) + .rejects.toThrow('Internal Server Error') + }) + }) + + describe('createOrder', () => { + it('should create an order successfully', async () => { + const orderData: CreateOrderRequest = { + buyer_id: 'buyer-1', + seller_id: 'seller-1', + items: [ + { product_id: 'prod-1', quantity: 2, unit_cents: 1500, batch: 'L001', expires_at: '2025-12-31' } + ], + shipping: { + recipient_name: 'Test User', + street: 'Rua Test', + number: '123', + district: 'Centro', + city: 'City', + state: 'ST', + zip_code: '12345-678', + country: 'Brasil' + }, + payment_method: 'pix' + } + + const mockOrder = { + id: 'new-order-1', + ...orderData, + status: 'Pendente', + total_cents: 3000, + created_at: new Date().toISOString() + } + + vi.mocked(apiClient.post).mockResolvedValue(mockOrder) + + const result = await ordersService.createOrder(orderData) + + expect(apiClient.post).toHaveBeenCalledWith('/v1/orders', orderData) + expect(result).toEqual(mockOrder) + }) + + it('should handle order creation failure', async () => { + vi.mocked(apiClient.post).mockRejectedValue(new Error('Insufficient stock')) + + const orderData: CreateOrderRequest = { + buyer_id: 'buyer-1', + seller_id: 'seller-1', + items: [], + shipping: { + recipient_name: '', + street: '', + number: '', + district: '', + city: '', + state: '', + zip_code: '', + country: '' + }, + payment_method: 'pix' + } + + await expect(ordersService.createOrder(orderData)) + .rejects.toThrow('Insufficient stock') + }) + }) +}) diff --git a/marketplace/src/services/shippingService.test.ts b/marketplace/src/services/shippingService.test.ts new file mode 100644 index 0000000..b78013c --- /dev/null +++ b/marketplace/src/services/shippingService.test.ts @@ -0,0 +1,96 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { shippingService } from './shippingService' +import { apiClient } from './apiClient' +import type { ShippingSettings } from '../types/shipping' + +// Mock apiClient +vi.mock('./apiClient', () => ({ + apiClient: { + get: vi.fn(), + post: vi.fn() + } +})) + +describe('shippingService', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('getSettings', () => { + it('should fetch shipping settings for a vendor', async () => { + const mockSettings: ShippingSettings = { + vendor_id: 'vendor-123', + active: true, + max_radius_km: 50, + price_per_km_cents: 150, + min_fee_cents: 1000, + pickup_active: true, + pickup_address: 'Rua Test, 123', + pickup_hours: '08:00-18:00', + latitude: -23.55, + longitude: -46.63 + } + + vi.mocked(apiClient.get).mockResolvedValue(mockSettings) + + const result = await shippingService.getSettings('vendor-123') + + expect(apiClient.get).toHaveBeenCalledWith('/v1/shipping/settings/vendor-123') + expect(result).toEqual(mockSettings) + expect(result.active).toBe(true) + expect(result.pickup_active).toBe(true) + }) + + it('should handle vendor not found', async () => { + vi.mocked(apiClient.get).mockRejectedValue(new Error('Vendor not found')) + + await expect(shippingService.getSettings('invalid-vendor')) + .rejects.toThrow('Vendor not found') + }) + }) + + describe('calculate', () => { + it('should calculate shipping cost', async () => { + const mockCalculation = { + options: [ + { + seller_id: 'seller-1', + delivery_fee_cents: 2500, + distance_km: 15.5, + estimated_days: 2, + pickup_available: true, + pickup_address: 'Rua Test, 123', + pickup_hours: '08:00-18:00' + } + ] + } + + vi.mocked(apiClient.post).mockResolvedValue(mockCalculation) + + const result = await shippingService.calculate({ + buyer_id: 'buyer-123', + order_total_cents: 15000, + items: [ + { seller_id: 'seller-1', product_id: 'prod-1', quantity: 2, price_cents: 7500 } + ] + }) + + expect(apiClient.post).toHaveBeenCalledWith('/v1/shipping/calculate', expect.objectContaining({ + buyer_id: 'buyer-123', + order_total_cents: 15000 + })) + expect(result.options).toHaveLength(1) + expect(result.options[0].delivery_fee_cents).toBe(2500) + }) + + it('should handle API error', async () => { + vi.mocked(apiClient.post).mockRejectedValue(new Error('Server error')) + + await expect(shippingService.calculate({ + buyer_id: 'buyer-123', + order_total_cents: 15000, + items: [] + })).rejects.toThrow('Server error') + }) + }) +}) diff --git a/marketplace/src/utils/format.test.ts b/marketplace/src/utils/format.test.ts new file mode 100644 index 0000000..2c433f7 --- /dev/null +++ b/marketplace/src/utils/format.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect } from 'vitest' +import { formatCents, formatCurrency } from './format' + +describe('format utilities', () => { + describe('formatCurrency', () => { + it('should format number to comma-separated string', () => { + expect(formatCurrency(10.00)).toBe('10,00') + expect(formatCurrency(15.99)).toBe('15,99') + expect(formatCurrency(1000.50)).toBe('1000,50') + }) + + it('should handle zero', () => { + expect(formatCurrency(0)).toBe('0,00') + }) + + it('should handle undefined/null', () => { + expect(formatCurrency(undefined)).toBe('0,00') + expect(formatCurrency(null)).toBe('0,00') + }) + + it('should handle NaN', () => { + expect(formatCurrency(NaN)).toBe('0,00') + }) + }) + + describe('formatCents', () => { + it('should format cents to BRL currency', () => { + expect(formatCents(1000)).toBe('R$ 10,00') + expect(formatCents(1599)).toBe('R$ 15,99') + expect(formatCents(100000)).toBe('R$ 1000,00') + }) + + it('should handle zero', () => { + expect(formatCents(0)).toBe('R$ 0,00') + }) + + it('should handle undefined/null', () => { + expect(formatCents(undefined)).toBe('R$ 0,00') + expect(formatCents(null)).toBe('R$ 0,00') + }) + + it('should handle NaN', () => { + expect(formatCents(NaN)).toBe('R$ 0,00') + }) + + it('should handle small amounts', () => { + expect(formatCents(50)).toBe('R$ 0,50') + expect(formatCents(1)).toBe('R$ 0,01') + }) + }) +})