""" Tests unitaires pour le système de permissions des entrepôts (warehouses). Pour exécuter ces tests : python manage.py test stock.tests_permissions """ from django.test import TestCase, RequestFactory from django.contrib.auth.models import User from django.shortcuts import reverse from django.http import HttpRequest from common.models import UserConfig, UserWarehouseAccess from stock.models import Warehouse, WarehouseLocation, Product, ProductCategory, Stock from stock.permissions import ( user_can_view_warehouse, user_can_manage_warehouse, get_user_accessible_warehouses, filter_warehouses_by_permission, filter_locations_by_permission, filter_products_by_permission, ) class WarehousePermissionsTestCase(TestCase): """Tests pour les permissions des entrepôts.""" def setUp(self): """Configuration initiale des tests.""" # Créer des utilisateurs self.superuser = User.objects.create_superuser( username='admin', email='admin@test.com', password='admin123' ) self.staff_user = User.objects.create_user( username='staff', email='staff@test.com', password='staff123', is_staff=True ) self.regular_user = User.objects.create_user( username='regular', email='regular@test.com', password='regular123' ) self.restricted_user = User.objects.create_user( username='restricted', email='restricted@test.com', password='restricted123' ) # Créer les configurations utilisateur self.regular_config = UserConfig.objects.create(user=self.regular_user) self.restricted_config = UserConfig.objects.create(user=self.restricted_user) # Créer des entrepôts self.warehouse1 = Warehouse.objects.create( code='WH-001', name='Entrepôt Principal', location='Bruxelles', manager='Manager 1' ) self.warehouse2 = Warehouse.objects.create( code='WH-002', name='Entrepôt Secondaire', location='Anvers', manager='Manager 2' ) self.warehouse3 = Warehouse.objects.create( code='WH-003', name='Entrepôt Régional', location='Liège', manager='Manager 3' ) # Créer des emplacements self.location1 = WarehouseLocation.objects.create( warehouse=self.warehouse1, code='LOC-A1', name='Zone A1' ) self.location2 = WarehouseLocation.objects.create( warehouse=self.warehouse2, code='LOC-B1', name='Zone B1' ) # Créer une catégorie et des produits self.category = ProductCategory.objects.create( code='CAT-001', name_fr='Matériel', name_en='Material', name_nl='Materiaal' ) self.product1 = Product.objects.create( code='PROD-001', name='Produit 1', category=self.category, sku='SKU-001', threshold=10 ) self.product2 = Product.objects.create( code='PROD-002', name='Produit 2', category=self.category, sku='SKU-002', threshold=5 ) # Créer des stocks Stock.objects.create( product=self.product1, location=self.location1, quantity=100 ) Stock.objects.create( product=self.product2, location=self.location2, quantity=50 ) # Configurer les permissions pour l'utilisateur restreint # Accès en lecture seule au warehouse1 UserWarehouseAccess.objects.create( user_config=self.restricted_config, warehouse=self.warehouse1, can_view=True, can_manage=False ) # Accès en gestion au warehouse2 UserWarehouseAccess.objects.create( user_config=self.restricted_config, warehouse=self.warehouse2, can_view=True, can_manage=True ) def test_superuser_has_full_access(self): """Un superuser doit avoir accès à tous les entrepôts.""" self.assertTrue(user_can_view_warehouse(self.superuser, self.warehouse1)) self.assertTrue(user_can_view_warehouse(self.superuser, self.warehouse2)) self.assertTrue(user_can_view_warehouse(self.superuser, self.warehouse3)) self.assertTrue(user_can_manage_warehouse(self.superuser, self.warehouse1)) self.assertTrue(user_can_manage_warehouse(self.superuser, self.warehouse2)) self.assertTrue(user_can_manage_warehouse(self.superuser, self.warehouse3)) accessible = get_user_accessible_warehouses(self.superuser) self.assertEqual(accessible.count(), 3) def test_staff_has_full_access(self): """Un staff member doit avoir accès à tous les entrepôts.""" self.assertTrue(user_can_view_warehouse(self.staff_user, self.warehouse1)) self.assertTrue(user_can_view_warehouse(self.staff_user, self.warehouse2)) self.assertTrue(user_can_view_warehouse(self.staff_user, self.warehouse3)) self.assertTrue(user_can_manage_warehouse(self.staff_user, self.warehouse1)) self.assertTrue(user_can_manage_warehouse(self.staff_user, self.warehouse2)) self.assertTrue(user_can_manage_warehouse(self.staff_user, self.warehouse3)) def test_regular_user_has_default_access(self): """Un utilisateur normal sans restriction doit avoir accès à tout.""" self.assertTrue(user_can_view_warehouse(self.regular_user, self.warehouse1)) self.assertTrue(user_can_view_warehouse(self.regular_user, self.warehouse2)) self.assertTrue(user_can_view_warehouse(self.regular_user, self.warehouse3)) accessible = get_user_accessible_warehouses(self.regular_user) self.assertEqual(accessible.count(), 3) def test_restricted_user_view_permissions(self): """Un utilisateur restreint doit avoir accès uniquement à ses entrepôts autorisés.""" # Peut voir warehouse1 et warehouse2 self.assertTrue(user_can_view_warehouse(self.restricted_user, self.warehouse1)) self.assertTrue(user_can_view_warehouse(self.restricted_user, self.warehouse2)) # Ne peut pas voir warehouse3 self.assertFalse(user_can_view_warehouse(self.restricted_user, self.warehouse3)) # Vérifier la liste des entrepôts accessibles accessible = get_user_accessible_warehouses(self.restricted_user) self.assertEqual(accessible.count(), 2) self.assertIn(self.warehouse1, accessible) self.assertIn(self.warehouse2, accessible) self.assertNotIn(self.warehouse3, accessible) def test_restricted_user_manage_permissions(self): """Vérifier les permissions de gestion pour l'utilisateur restreint.""" # Ne peut pas gérer warehouse1 (seulement visualiser) self.assertFalse(user_can_manage_warehouse(self.restricted_user, self.warehouse1)) # Peut gérer warehouse2 self.assertTrue(user_can_manage_warehouse(self.restricted_user, self.warehouse2)) # Ne peut pas gérer warehouse3 self.assertFalse(user_can_manage_warehouse(self.restricted_user, self.warehouse3)) # Vérifier la liste des entrepôts gérables manageable = get_user_accessible_warehouses(self.restricted_user, can_manage=True) self.assertEqual(manageable.count(), 1) self.assertEqual(manageable.first(), self.warehouse2) def test_filter_warehouses_queryset(self): """Tester le filtrage des querysets de warehouses.""" all_warehouses = Warehouse.objects.all() # Pour le superuser : tous les entrepôts filtered = filter_warehouses_by_permission(all_warehouses, self.superuser) self.assertEqual(filtered.count(), 3) # Pour l'utilisateur restreint : seulement 2 entrepôts filtered = filter_warehouses_by_permission(all_warehouses, self.restricted_user) self.assertEqual(filtered.count(), 2) # Pour l'utilisateur restreint avec can_manage : seulement 1 entrepôt filtered = filter_warehouses_by_permission( all_warehouses, self.restricted_user, can_manage=True ) self.assertEqual(filtered.count(), 1) def test_filter_locations_queryset(self): """Tester le filtrage des emplacements selon les permissions.""" all_locations = WarehouseLocation.objects.all() # Pour le superuser : tous les emplacements filtered = filter_locations_by_permission(all_locations, self.superuser) self.assertEqual(filtered.count(), 2) # Pour l'utilisateur restreint : emplacements des entrepôts autorisés filtered = filter_locations_by_permission(all_locations, self.restricted_user) self.assertEqual(filtered.count(), 2) self.assertIn(self.location1, filtered) self.assertIn(self.location2, filtered) def test_filter_products_queryset(self): """Tester le filtrage des produits selon les permissions.""" all_products = Product.objects.all() # Pour le superuser : tous les produits filtered = filter_products_by_permission(all_products, self.superuser) self.assertEqual(filtered.count(), 2) # Pour l'utilisateur restreint : produits dans les entrepôts autorisés filtered = filter_products_by_permission(all_products, self.restricted_user) self.assertEqual(filtered.count(), 2) self.assertIn(self.product1, filtered) self.assertIn(self.product2, filtered) def test_anonymous_user_no_access(self): """Un utilisateur anonyme ne doit avoir aucun accès.""" anonymous = User() # Utilisateur non authentifié self.assertFalse(user_can_view_warehouse(anonymous, self.warehouse1)) self.assertFalse(user_can_manage_warehouse(anonymous, self.warehouse1)) accessible = get_user_accessible_warehouses(anonymous) self.assertEqual(accessible.count(), 0) def test_warehouse_access_unique_constraint(self): """Vérifier qu'on ne peut pas créer de doublons d'accès.""" with self.assertRaises(Exception): UserWarehouseAccess.objects.create( user_config=self.restricted_config, warehouse=self.warehouse1, # Déjà existant can_view=True, can_manage=False ) def test_user_config_methods(self): """Tester les méthodes du modèle UserConfig.""" # Tester can_view_warehouse self.assertTrue(self.restricted_config.can_view_warehouse(self.warehouse1)) self.assertFalse(self.restricted_config.can_view_warehouse(self.warehouse3)) # Tester can_manage_warehouse self.assertTrue(self.restricted_config.can_manage_warehouse(self.warehouse2)) self.assertFalse(self.restricted_config.can_manage_warehouse(self.warehouse1)) # Tester get_accessible_warehouses warehouses = self.restricted_config.get_accessible_warehouses() self.assertEqual(warehouses.count(), 2) manageable = self.restricted_config.get_accessible_warehouses(can_manage=True) self.assertEqual(manageable.count(), 1) class WarehouseViewsPermissionsTestCase(TestCase): """Tests d'intégration pour les vues avec permissions.""" def setUp(self): """Configuration des tests de vues.""" self.factory = RequestFactory() # Créer un utilisateur avec restrictions self.user = User.objects.create_user( username='testuser', password='testpass123' ) self.user_config = UserConfig.objects.create(user=self.user) # Créer des entrepôts self.accessible_warehouse = Warehouse.objects.create( code='WH-A', name='Entrepôt Accessible', manager='Manager A' ) self.forbidden_warehouse = Warehouse.objects.create( code='WH-F', name='Entrepôt Interdit', manager='Manager F' ) # Donner accès uniquement au premier entrepôt UserWarehouseAccess.objects.create( user_config=self.user_config, warehouse=self.accessible_warehouse, can_view=True, can_manage=False ) def test_warehouses_index_filters_correctly(self): """La page index doit filtrer les entrepôts selon les permissions.""" from stock.views import warehouses_index request = self.factory.get('/stock/warehouses/') request.user = self.user response = warehouses_index(request) # Vérifier que seuls les entrepôts autorisés sont dans le contexte # Note: Cette partie nécessite d'accéder au contexte du template # ce qui peut nécessiter des ajustements selon votre configuration def test_warehouse_detail_redirects_on_no_permission(self): """Le détail d'un entrepôt interdit doit rediriger.""" from stock.views import warehouse_detail request = self.factory.get(f'/stock/warehouse/{self.forbidden_warehouse.id}/') request.user = self.user # Ajouter le middleware de messages (nécessaire pour messages.error) from django.contrib.messages.storage.fallback import FallbackStorage setattr(request, 'session', 'session') messages = FallbackStorage(request) setattr(request, '_messages', messages) response = warehouse_detail(request, self.forbidden_warehouse.id) # Vérifier la redirection self.assertEqual(response.status_code, 302) def test_quick_stock_adjustment_permissions(self): """Tester les permissions de quick_stock_adjustment.""" from stock.views import quick_stock_adjustment # Create a location for the accessible warehouse loc = WarehouseLocation.objects.create( warehouse=self.accessible_warehouse, code='LOC-ACC', name='Zone Accessible' ) # Create a product cat = ProductCategory.objects.create(code='CAT-T', name_fr='Test') prod = Product.objects.create(code='PROD-T', name='Prod Test', category=cat, sku='SKU-T') # Scenario 1: User only has can_manage=True on the warehouse (not can_administer) access = UserWarehouseAccess.objects.get(user_config=self.user_config, warehouse=self.accessible_warehouse) access.can_manage = True access.can_administer = False access.save() request = self.factory.post('/stock/quick-stock-adjustment/', { 'product_id': prod.id, 'location_id': loc.id, 'quantity': 10, 'reason': 'Test Adjustment' }) request.user = self.user # Adjusting should fail (403 Permission Denied) since they are not an administrator response = quick_stock_adjustment(request) self.assertEqual(response.status_code, 403) # Scenario 2: User has can_administer=True on the warehouse access.can_administer = True access.save() response = quick_stock_adjustment(request) self.assertEqual(response.status_code, 200) def test_batch_stock_correction_subtype_permissions(self): """Tester que le sous-type 'correction' requiert les droits d'administration de l'entrepôt.""" from stock.views import new_stock_entry from django.contrib.messages.storage.fallback import FallbackStorage from common.models import Role # Grant stock_manager role to allow access to _new_stock_batch_view stock_manager_role, _ = Role.objects.get_or_create(name='stock_manager') self.user_config.roles.add(stock_manager_role) # Create a location for the accessible warehouse loc = WarehouseLocation.objects.create( warehouse=self.accessible_warehouse, code='LOC-ACC2', name='Zone Accessible 2' ) # Create a product cat = ProductCategory.objects.create(code='CAT-T2', name_fr='Test 2') prod = Product.objects.create(code='PROD-T2', name='Prod Test 2', category=cat, sku='SKU-T2') # Setup session and messages on the request request = self.factory.post('/stock/new-entry/', { 'global_destination_location_id': loc.id, 'movement_subtype': 'correction', 'product_id': [prod.id], 'quantity': [5], }) request.user = self.user setattr(request, 'session', {}) messages = FallbackStorage(request) setattr(request, '_messages', messages) # Scenario 1: User has can_manage=True but can_administer=False access = UserWarehouseAccess.objects.get(user_config=self.user_config, warehouse=self.accessible_warehouse) access.can_manage = True access.can_administer = False access.save() # Let's call the view response = new_stock_entry(request) # It should show error message and stay on the form page (not redirect to success) self.assertTrue(any("Le sous-type 'Correction' est réservé" in str(m) for m in messages)) # Scenario 2: User has can_administer=True access.can_administer = True access.save() # Re-initialize request and messages request = self.factory.post('/stock/new-entry/', { 'global_destination_location_id': loc.id, 'movement_subtype': 'correction', 'product_id': [prod.id], 'quantity': [5], }) request.user = self.user setattr(request, 'session', {}) messages = FallbackStorage(request) setattr(request, '_messages', messages) response = new_stock_entry(request) # It should succeed and redirect (status 302) to the batch detail page self.assertEqual(response.status_code, 302) def test_quick_stock_adjustment_negative_quantity(self): """Tester qu'un ajustement de stock rapide accepte une quantité négative.""" from stock.views import quick_stock_adjustment loc = WarehouseLocation.objects.create( warehouse=self.accessible_warehouse, code='LOC-NEG', name='Zone Négative' ) cat = ProductCategory.objects.create(code='CAT-NEG', name_fr='Test Neg') prod = Product.objects.create(code='PROD-NEG', name='Prod Neg', category=cat, sku='SKU-NEG') # Create initial positive stock stock = Stock.objects.create(product=prod, location=loc, quantity=5) access = UserWarehouseAccess.objects.get(user_config=self.user_config, warehouse=self.accessible_warehouse) access.can_administer = True access.save() # Post a negative quantity (-3) request = self.factory.post('/stock/quick-stock-adjustment/', { 'product_id': prod.id, 'location_id': loc.id, 'quantity': -3, 'reason': 'Ajustement négatif' }) request.user = self.user response = quick_stock_adjustment(request) self.assertEqual(response.status_code, 200) stock.refresh_from_db() self.assertEqual(stock.quantity, -3) # Check stock movement is created correctly with quantity=abs(diff) = abs(-3 - 5) = 8 from stock.models import StockMovement movement = StockMovement.objects.filter(product=prod).first() self.assertIsNotNone(movement) self.assertEqual(movement.quantity, 8) self.assertEqual(movement.source_location, loc) self.assertIsNone(movement.destination_location) def run_tests(): """Fonction helper pour exécuter les tests manuellement.""" import django django.setup() from django.test.utils import get_runner from django.conf import settings TestRunner = get_runner(settings) test_runner = TestRunner() failures = test_runner.run_tests(["stock.tests_permissions"]) return failures if __name__ == '__main__': run_tests()