client = static::createClient(); $entityManager = $this->client->getContainer()->get(EntityManagerInterface::class); self::assertInstanceOf(EntityManagerInterface::class, $entityManager); $this->entityManager = $entityManager; $metadata = $this->entityManager->getMetadataFactory()->getAllMetadata(); $schemaTool = new SchemaTool($this->entityManager); $schemaTool->dropDatabase(); if ($metadata !== []) { $schemaTool->createSchema($metadata); } new SignalFixtures()->load($this->entityManager); } #[\Override] protected function tearDown(): void { parent::tearDown(); if (isset($this->entityManager)) { $this->entityManager->close(); unset($this->entityManager); } } public function testIndexReturnsRecentSignals(): void { $this->client->request('GET', '/api/signals', server: [ 'HTTP_ACCEPT' => 'application/json', ]); self::assertResponseIsSuccessful(); $content = $this->client->getResponse()->getContent(); self::assertIsString($content); $payload = json_decode($content, true, 512, JSON_THROW_ON_ERROR); self::assertSame(substr(hash('sha256', '127.0.0.1'), 0, 12), $payload['clientKey']); self::assertCount(3, $payload['points']); self::assertSame(3, $payload['totals']['points']); self::assertSame(2, $payload['totals']['contributors']); self::assertSame(-11.6852, $payload['points'][0]['signalLocation']['lat']); } public function testIndexRespectsLimitQueryParameter(): void { $this->client->request('GET', '/api/signals?limit=1', server: [ 'HTTP_ACCEPT' => 'application/json', ]); self::assertResponseIsSuccessful(); $content = $this->client->getResponse()->getContent(); self::assertIsString($content); $payload = json_decode($content, true, 512, JSON_THROW_ON_ERROR); self::assertCount(1, $payload['points']); self::assertSame(1, $payload['totals']['points']); } public function testStorePersistsNewSignal(): void { $body = [ 'signalLocation' => [ 'lat' => -11.6901, 'lng' => 27.4959, ], 'userLocation' => [ 'lat' => -11.6899, 'lng' => 27.4962, ], ]; $this->client->request('POST', '/api/signals', server: [ 'CONTENT_TYPE' => 'application/json', 'HTTP_ACCEPT' => 'application/json', ], content: json_encode($body, JSON_THROW_ON_ERROR)); self::assertResponseStatusCodeSame(Response::HTTP_CREATED); $content = $this->client->getResponse()->getContent(); self::assertIsString($content); $payload = json_decode($content, true, 512, JSON_THROW_ON_ERROR); self::assertSame('stored', $payload['status']); self::assertSame($body['signalLocation']['lat'], $payload['point']['signalLocation']['lat']); self::assertSame($body['signalLocation']['lng'], $payload['point']['signalLocation']['lng']); self::assertSame($body['userLocation']['lat'], $payload['point']['userLocation']['lat']); self::assertSame($body['userLocation']['lng'], $payload['point']['userLocation']['lng']); $repository = $this->entityManager->getRepository(Signal::class); $signals = $repository->findAll(); self::assertCount(4, $signals); } public function testStoreRejectsInvalidCoordinates(): void { $body = [ 'signalLocation' => [ 'lat' => 181, 'lng' => 10, ], 'userLocation' => [ 'lat' => 10, 'lng' => 10, ], ]; $this->client->request('POST', '/api/signals', server: [ 'CONTENT_TYPE' => 'application/json', 'HTTP_ACCEPT' => 'application/json', ], content: json_encode($body, JSON_THROW_ON_ERROR)); self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); $response = $this->client->getResponse(); self::assertSame('invalid_coordinates', $response->headers->get('x-error-code')); $content = $response->getContent(); self::assertIsString($content); $payload = json_decode($content, true, 512, JSON_THROW_ON_ERROR); self::assertSame('Latitude 181.000000 is out of bounds.', $payload['detail'] ?? null); } public function testStoreRejectsPointTooFar(): void { $body = [ 'signalLocation' => [ 'lat' => -11.6901, 'lng' => 27.4959, ], 'userLocation' => [ 'lat' => -11.7005, 'lng' => 27.4804, ], ]; $this->client->request('POST', '/api/signals', server: [ 'CONTENT_TYPE' => 'application/json', 'HTTP_ACCEPT' => 'application/json', ], content: json_encode($body, JSON_THROW_ON_ERROR)); self::assertResponseStatusCodeSame(Response::HTTP_UNPROCESSABLE_ENTITY); $response = $this->client->getResponse(); self::assertSame('point_too_far', $response->headers->get('x-error-code')); $content = $response->getContent(); self::assertIsString($content); $payload = json_decode($content, true, 512, JSON_THROW_ON_ERROR); self::assertSame('The submitted point must be within 1.00km of your location.', $payload['detail'] ?? null); } }