# tests/test_stock_service.py import pytest from app.services.stock_service import StockService from models.api_models import StockCreate, StockAdd, StockUpdate from models.database_models import Stock, Product class TestStockService: def setup_method(self): self.service = StockService() def test_normalize_gtin_valid(self): """Test GTIN normalization with valid GTINs""" # Test various valid GTIN formats assert self.service.normalize_gtin("1234567890123") == "1234567890123" assert self.service.normalize_gtin("123456789012") == "123456789012" assert self.service.normalize_gtin("12345678") == "12345678" def test_normalize_gtin_invalid(self): """Test GTIN normalization with invalid GTINs""" assert self.service.normalize_gtin("invalid") is None assert self.service.normalize_gtin("123") is None assert self.service.normalize_gtin("") is None assert self.service.normalize_gtin(None) is None def test_set_stock_new_entry(self, db): """Test setting stock for a new GTIN/location combination""" stock_data = StockCreate( gtin="1234567890123", location="WAREHOUSE_A", quantity=100 ) result = self.service.set_stock(db, stock_data) assert result.gtin == "1234567890123" assert result.location == "WAREHOUSE_A" assert result.quantity == 100 def test_set_stock_existing_entry(self, db, test_stock): """Test setting stock for an existing GTIN/location combination""" stock_data = StockCreate( gtin=test_stock.gtin, location=test_stock.location, quantity=200 ) result = self.service.set_stock(db, stock_data) assert result.gtin == test_stock.gtin assert result.location == test_stock.location assert result.quantity == 200 # Should replace the original quantity def test_set_stock_invalid_gtin(self, db): """Test setting stock with invalid GTIN""" stock_data = StockCreate( gtin="invalid_gtin", location="WAREHOUSE_A", quantity=100 ) with pytest.raises(ValueError, match="Invalid GTIN format"): self.service.set_stock(db, stock_data) def test_add_stock_new_entry(self, db): """Test adding stock for a new GTIN/location combination""" stock_data = StockAdd( gtin="1234567890123", location="WAREHOUSE_B", quantity=50 ) result = self.service.add_stock(db, stock_data) assert result.gtin == "1234567890123" assert result.location == "WAREHOUSE_B" assert result.quantity == 50 def test_add_stock_existing_entry(self, db, test_stock): """Test adding stock to an existing GTIN/location combination""" original_quantity = test_stock.quantity stock_data = StockAdd( gtin=test_stock.gtin, location=test_stock.location, quantity=25 ) result = self.service.add_stock(db, stock_data) assert result.gtin == test_stock.gtin assert result.location == test_stock.location assert result.quantity == original_quantity + 25 def test_add_stock_invalid_gtin(self, db): """Test adding stock with invalid GTIN""" stock_data = StockAdd( gtin="invalid_gtin", location="WAREHOUSE_A", quantity=50 ) with pytest.raises(ValueError, match="Invalid GTIN format"): self.service.add_stock(db, stock_data) def test_remove_stock_success(self, db, test_stock): """Test removing stock successfully""" original_quantity = test_stock.quantity remove_quantity = 10 stock_data = StockAdd( gtin=test_stock.gtin, location=test_stock.location, quantity=remove_quantity ) result = self.service.remove_stock(db, stock_data) assert result.gtin == test_stock.gtin assert result.location == test_stock.location assert result.quantity == original_quantity - remove_quantity def test_remove_stock_insufficient_stock(self, db, test_stock): """Test removing more stock than available""" stock_data = StockAdd( gtin=test_stock.gtin, location=test_stock.location, quantity=test_stock.quantity + 10 # More than available ) with pytest.raises(ValueError, match="Insufficient stock"): self.service.remove_stock(db, stock_data) def test_remove_stock_nonexistent_entry(self, db): """Test removing stock from non-existent GTIN/location""" stock_data = StockAdd( gtin="9999999999999", location="NONEXISTENT", quantity=10 ) with pytest.raises(ValueError, match="No stock found"): self.service.remove_stock(db, stock_data) def test_remove_stock_invalid_gtin(self, db): """Test removing stock with invalid GTIN""" stock_data = StockAdd( gtin="invalid_gtin", location="WAREHOUSE_A", quantity=10 ) with pytest.raises(ValueError, match="Invalid GTIN format"): self.service.remove_stock(db, stock_data) def test_get_stock_by_gtin_success(self, db, test_stock, test_product): """Test getting stock summary by GTIN""" result = self.service.get_stock_by_gtin(db, test_stock.gtin) assert result.gtin == test_stock.gtin assert result.total_quantity == test_stock.quantity assert len(result.locations) == 1 assert result.locations[0].location == test_stock.location assert result.locations[0].quantity == test_stock.quantity assert result.product_title == test_product.title def test_get_stock_by_gtin_multiple_locations(self, db): """Test getting stock summary with multiple locations""" gtin = "1234567890123" # Create multiple stock entries for the same GTIN stock1 = Stock(gtin=gtin, location="WAREHOUSE_A", quantity=50) stock2 = Stock(gtin=gtin, location="WAREHOUSE_B", quantity=30) db.add(stock1) db.add(stock2) db.commit() result = self.service.get_stock_by_gtin(db, gtin) assert result.gtin == gtin assert result.total_quantity == 80 assert len(result.locations) == 2 def test_get_stock_by_gtin_not_found(self, db): """Test getting stock for non-existent GTIN""" with pytest.raises(ValueError, match="No stock found"): self.service.get_stock_by_gtin(db, "9999999999999") def test_get_stock_by_gtin_invalid_gtin(self, db): """Test getting stock with invalid GTIN""" with pytest.raises(ValueError, match="Invalid GTIN format"): self.service.get_stock_by_gtin(db, "invalid_gtin") def test_get_total_stock_success(self, db, test_stock, test_product): """Test getting total stock for a GTIN""" result = self.service.get_total_stock(db, test_stock.gtin) assert result["gtin"] == test_stock.gtin assert result["total_quantity"] == test_stock.quantity assert result["product_title"] == test_product.title assert result["locations_count"] == 1 def test_get_total_stock_invalid_gtin(self, db): """Test getting total stock with invalid GTIN""" with pytest.raises(ValueError, match="Invalid GTIN format"): self.service.get_total_stock(db, "invalid_gtin") def test_get_all_stock_no_filters(self, db, test_stock): """Test getting all stock without filters""" result = self.service.get_all_stock(db) assert len(result) >= 1 assert any(stock.gtin == test_stock.gtin for stock in result) def test_get_all_stock_with_location_filter(self, db, test_stock): """Test getting all stock with location filter""" result = self.service.get_all_stock(db, location=test_stock.location) assert len(result) >= 1 assert all(stock.location.upper() == test_stock.location.upper() for stock in result) def test_get_all_stock_with_gtin_filter(self, db, test_stock): """Test getting all stock with GTIN filter""" result = self.service.get_all_stock(db, gtin=test_stock.gtin) assert len(result) >= 1 assert all(stock.gtin == test_stock.gtin for stock in result) def test_get_all_stock_with_pagination(self, db): """Test getting all stock with pagination""" # Create multiple stock entries for i in range(5): stock = Stock( gtin=f"123456789012{i}", location=f"WAREHOUSE_{i}", quantity=10 ) db.add(stock) db.commit() result = self.service.get_all_stock(db, skip=2, limit=2) assert len(result) == 2 def test_update_stock_success(self, db, test_stock): """Test updating stock quantity""" stock_update = StockUpdate(quantity=150) result = self.service.update_stock(db, test_stock.id, stock_update) assert result.id == test_stock.id assert result.quantity == 150 def test_update_stock_not_found(self, db): """Test updating non-existent stock entry""" stock_update = StockUpdate(quantity=150) with pytest.raises(ValueError, match="Stock entry not found"): self.service.update_stock(db, 99999, stock_update) def test_delete_stock_success(self, db, test_stock): """Test deleting stock entry""" stock_id = test_stock.id result = self.service.delete_stock(db, stock_id) assert result is True # Verify the stock is actually deleted deleted_stock = db.query(Stock).filter(Stock.id == stock_id).first() assert deleted_stock is None def test_delete_stock_not_found(self, db): """Test deleting non-existent stock entry""" with pytest.raises(ValueError, match="Stock entry not found"): self.service.delete_stock(db, 99999) def test_get_stock_by_id_success(self, db, test_stock): """Test getting stock entry by ID""" result = self.service.get_stock_by_id(db, test_stock.id) assert result is not None assert result.id == test_stock.id assert result.gtin == test_stock.gtin def test_get_stock_by_id_not_found(self, db): """Test getting non-existent stock entry by ID""" result = self.service.get_stock_by_id(db, 99999) assert result is None # Additional fixtures that might be needed for stock tests @pytest.fixture def test_stock(db): """Create a test stock entry""" stock = Stock( gtin="1234567890123", location="WAREHOUSE_MAIN", quantity=50 ) db.add(stock) db.commit() db.refresh(stock) return stock @pytest.fixture def test_product_with_stock(db, test_stock): """Create a test product that corresponds to the test stock""" product = Product( product_id="STOCK_TEST_001", title="Stock Test Product", gtin=test_stock.gtin, price="29.99", brand="TestBrand", marketplace="Letzshop" ) db.add(product) db.commit() db.refresh(product) return product