import json from datetime import timedelta from unittest import mock from django.test import TestCase from django.contrib.auth import get_user_model from django.contrib import messages from django.contrib.messages import get_messages from django.core.files.uploadedfile import SimpleUploadedFile from common.models import UserConfig, Role, Thematic, UserThematics from contracts.models import Company, CompanyTeam, CompanyMember, Contract, ContractPost, ContractOrder, ContractOrderQuote, ContractOrderQuoteItem from assets.models import AssetCategory from interventions.models import ( Intervention, InterventionContractPost, InterventionPlanificationTimeLine, InterventionRealizationTimeLine, Operation, InterventionAsset, OperationTemplate, OperationTemplateItem, OperationStatusSet, OperationStatus, STATUS_ORDERS, InterventionDocument, ) from django.contrib.contenttypes.models import ContentType from interventions.views import _group_operations_by_asset_and_template, _create_equipment_operations_for_assets from interventions.permissions import can_manage_occupations, can_delete_document from django.urls import reverse from django.utils import timezone from decimal import Decimal class MobileInterventionAssetsApiTests(TestCase): def setUp(self): self.User = get_user_model() self.user = self.User.objects.create_user(username='mobile-admin', password='pwd') self.role_admin = Role.objects.create(name='admin') self.user_config = UserConfig.objects.create(user=self.user, is_intern=True) self.user_config.roles.add(self.role_admin) self.thematic = Thematic.objects.create(code='road', name_fr='Route', name_nl='Weg') self.category = AssetCategory.objects.create( code='CAT1', name_fr='Catégorie 1', name_nl='Categorie 1', thematic=self.thematic, ) self.intervention = Intervention.objects.create( title='Mobile assets', status='to_be_processed', thematic=self.thematic, ) self.filters_url = reverse('interventions:mobile_intervention_assets_filters', args=[self.intervention.id]) self.search_url = reverse('interventions:mobile_intervention_assets_search', args=[self.intervention.id]) self.sync_url = reverse('interventions:mobile_intervention_assets_sync', args=[self.intervention.id]) self.client.login(username='mobile-admin', password='pwd') def test_filters_endpoint_returns_categories(self): with mock.patch('interventions.views._get_asset_model_classes', return_value=[]): response = self.client.get(self.filters_url) self.assertEqual(response.status_code, 200) payload = response.json() self.assertTrue(payload.get('success')) self.assertEqual(len(payload.get('categories', [])), 1) self.assertEqual(payload['categories'][0]['id'], self.category.id) def test_search_endpoint_wraps_helper(self): fake_results = { 'assets': [], 'match_count': 0, 'has_more': False, 'requires_category': False, 'has_locations': False, } with mock.patch('interventions.views._search_mobile_assets', return_value=fake_results) as mocked: response = self.client.get(self.search_url, {'q': 'lamp'}) self.assertEqual(response.status_code, 200) self.assertTrue(response.json().get('success')) self.assertEqual(response.json().get('results'), fake_results) mocked.assert_called_once() def test_sync_endpoint_returns_stats(self): stats = { 'created': 1, 'deleted': 0, 'operations_created': 0, 'operations_deleted': 0, 'assets_with_values': 0, 'target_total': 1, 'requested_assets': 1, } with mock.patch('interventions.views._sync_intervention_assets_data', return_value=stats) as mocked: response = self.client.post( self.sync_url, data=json.dumps({'assets': [{'model': 'signstreet', 'object_id': 42}]}), content_type='application/json', ) self.assertEqual(response.status_code, 200) payload = response.json() self.assertTrue(payload.get('success')) self.assertEqual(payload.get('stats'), stats) mocked.assert_called_once() class MobileInterventionPauseTests(TestCase): def setUp(self): User = get_user_model() self.user = User.objects.create_user(username='mobileuser', password='pwd') # Give admin role to bypass permissions self.role_admin = Role.objects.create(name='admin') self.user_config = UserConfig.objects.create(user=self.user, is_intern=True) self.user_config.roles.add(self.role_admin) # Create an intervention in progress self.intervention = Intervention.objects.create(title='Mobile Pause', status='in_progress') # Optional: link a company member to the user self.company = Company.objects.create(name='MobileCo') self.team = CompanyTeam.objects.create(name='Team M', company=self.company) self.member = CompanyMember.objects.create(user=self.user, name='Mobile User') self.member.teams.add(self.team) def test_pause_reason_is_persisted_from_mobile_detail(self): self.client.login(username='mobileuser', password='pwd') url = reverse('mobile:intervention_detail_mobile', args=[self.intervention.id]) resp = self.client.post(url, { 'status_action': 'pause', 'pause_reason': 'other', 'pause_reason_other': 'Pluie abondante', }) # Redirect back to detail self.assertEqual(resp.status_code, 302) # Verify persisted values self.intervention.refresh_from_db() self.assertEqual(self.intervention.status, 'on_pause') self.assertEqual(self.intervention.pause_reason, 'other') self.assertEqual(self.intervention.pause_reason_other, 'Pluie abondante')