authenticatedClient('admin', 'admin'); $tierClient = $this->seedClient('Ferme Pin'); $this->seedClientAddress($tierClient, 47.218, -1.553); $tierSupplier = $this->seedSupplier('Negoce Pin'); $this->seedSupplierAddress($tierSupplier, 47.220, -1.560); $data = $client->request('GET', '/api/visitable_tiers', [ 'headers' => ['Accept' => self::LD], 'query' => ['q' => self::ISOLATE_Q], ])->toArray(); self::assertSame(2, $data['totalItems'], 'Une adresse geolocalisee Client + une Fournisseur = 2 pins.'); $byType = []; foreach ($data['member'] as $pin) { $byType[$pin['tierType']] = $pin; } self::assertArrayHasKey('client', $byType); self::assertArrayHasKey('supplier', $byType); self::assertSame((float) 47.218, $byType['client']['latitude']); self::assertSame($tierClient->getId(), $byType['client']['tierId']); self::assertStringContainsString('NANTES', $byType['client']['address']); self::assertSame('client-'.$byType['client']['addressId'], $byType['client']['id']); } public function testExcludesAddressesWithoutCoordinates(): void { $client = $this->authenticatedClient('admin', 'admin'); $tier = $this->seedClient('Sans Geo'); // Adresse sans lat/lng (RG-6.05 : exclue du calcul/carte). $this->seedClientAddress($tier, 0, 0); $this->getEm()->getConnection()->executeStatement( "UPDATE client_address SET latitude = NULL, longitude = NULL WHERE city = 'NANTES' AND street = '1 rue de Test' AND client_id = :id", ['id' => $tier->getId()], ); $data = $client->request('GET', '/api/visitable_tiers', [ 'headers' => ['Accept' => self::LD], 'query' => ['q' => self::ISOLATE_Q], ])->toArray(); self::assertSame(0, $data['totalItems'], 'Une adresse sans coordonnees ne produit aucun pin (RG-6.05).'); } public function testBboxRestrictsToVisibleArea(): void { $client = $this->authenticatedClient('admin', 'admin'); $inside = $this->seedClient('Dedans'); $this->seedClientAddress($inside, 47.0, -1.5); $outside = $this->seedClient('Dehors'); $this->seedClientAddress($outside, 10.0, 10.0); // bbox = minLng,minLat,maxLng,maxLat autour du point « dedans ». $data = $client->request('GET', '/api/visitable_tiers', [ 'headers' => ['Accept' => self::LD], 'query' => ['q' => self::ISOLATE_Q, 'bbox' => '-2.0,46.5,-1.0,47.5'], ])->toArray(); self::assertSame(1, $data['totalItems'], 'Seul le pin dans la bbox est retourne.'); self::assertSame($inside->getId(), $data['member'][0]['tierId']); } public function testTypeFilterRestrictsTiers(): void { $client = $this->authenticatedClient('admin', 'admin'); $this->seedClientAddress($this->seedClient('Client T')); $this->seedSupplierAddress($this->seedSupplier('Supplier T')); $data = $client->request('GET', '/api/visitable_tiers', [ 'headers' => ['Accept' => self::LD], 'query' => ['q' => self::ISOLATE_Q, 'type' => 'supplier'], ])->toArray(); self::assertSame(1, $data['totalItems'], 'type=supplier ne retourne que les fournisseurs.'); self::assertSame('supplier', $data['member'][0]['tierType']); } public function testInvalidTypeReturns400(): void { $client = $this->authenticatedClient('admin', 'admin'); $client->request('GET', '/api/visitable_tiers', [ 'headers' => ['Accept' => self::LD], 'query' => ['type' => 'prestataire_inexistant'], ]); self::assertResponseStatusCodeSame(400, 'Un type hors whitelist est rejete en 400.'); } public function testMalformedBboxReturns400(): void { $client = $this->authenticatedClient('admin', 'admin'); $client->request('GET', '/api/visitable_tiers', [ 'headers' => ['Accept' => self::LD], 'query' => ['bbox' => '1,2,3'], ]); self::assertResponseStatusCodeSame(400, 'Une bbox a 3 valeurs est rejetee en 400.'); } public function testExcludesArchivedTier(): void { $client = $this->authenticatedClient('admin', 'admin'); $tier = $this->seedClient('Archive'); $this->seedClientAddress($tier); $this->getEm()->getConnection()->executeStatement( 'UPDATE client SET is_archived = TRUE WHERE id = :id', ['id' => $tier->getId()], ); $data = $client->request('GET', '/api/visitable_tiers', [ 'headers' => ['Accept' => self::LD], 'query' => ['q' => self::ISOLATE_Q], ])->toArray(); self::assertSame(0, $data['totalItems'], 'Un Tiers archive ne produit aucun pin.'); } public function testRequiresViewPermission(): void { $creds = $this->createUserWithPermission('core.users.view'); $client = $this->authenticatedClient($creds['username'], $creds['password']); $client->request('GET', '/api/visitable_tiers', ['headers' => ['Accept' => self::LD]]); self::assertResponseStatusCodeSame(403, 'Sans field_sales.tours.view -> 403.'); } public function testPaginationFalseReturnsAllPins(): void { $client = $this->authenticatedClient('admin', 'admin'); for ($i = 0; $i < 12; ++$i) { $this->seedClientAddress($this->seedClient('Bulk '.$i), 47.0 + $i / 1000, -1.5); } $data = $client->request('GET', '/api/visitable_tiers', [ 'headers' => ['Accept' => self::LD], 'query' => ['q' => self::ISOLATE_Q, 'pagination' => 'false'], ])->toArray(); self::assertCount(12, $data['member'], 'pagination=false retourne tous les pins de la zone (carte).'); } }