Skip to content

Commit 12304ba

Browse files
committed
feat(stations): add measurement aggregate retrieval
1 parent 72c9c08 commit 12304ba

2 files changed

Lines changed: 126 additions & 0 deletions

File tree

src/Resource/Stations.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
namespace ProgrammatorDev\OpenWeatherMap\Resource;
44

55
use ProgrammatorDev\Api\Resource;
6+
use ProgrammatorDev\OpenWeatherMap\Entity\Stations\MeasurementAggregate;
67
use ProgrammatorDev\OpenWeatherMap\Entity\Stations\Station;
8+
use ProgrammatorDev\OpenWeatherMap\Enum\AggregationInterval;
79
use ProgrammatorDev\OpenWeatherMap\Request\Stations\Measurement;
810
use ProgrammatorDev\OpenWeatherMap\Validation\Assert;
911

@@ -141,6 +143,34 @@ public function submitMeasurements(
141143
->post('/data/3.0/measurements');
142144
}
143145

146+
/**
147+
* @return list<MeasurementAggregate>
148+
*/
149+
public function measurements(
150+
string $stationId,
151+
AggregationInterval $interval,
152+
\DateTimeInterface $startAt,
153+
\DateTimeInterface $endAt,
154+
int $limit,
155+
): array {
156+
$stationId = Assert::notBlank($stationId, 'station ID');
157+
Assert::chronologicalRange($startAt, $endAt);
158+
$limit = Assert::positiveInteger($limit, 'result limit');
159+
160+
// https://openweathermap.org/api/stations#measurement
161+
return $this
162+
->endpoint()
163+
->queries([
164+
'station_id' => $stationId,
165+
'type' => $interval,
166+
'limit' => $limit,
167+
'from' => $startAt->getTimestamp(),
168+
'to' => $endAt->getTimestamp(),
169+
])
170+
->get('/data/3.0/measurements')
171+
->collection(MeasurementAggregate::class);
172+
}
173+
144174
/**
145175
* @return array{
146176
* external_id: string,

tests/Unit/Resource/StationsTest.php

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
namespace ProgrammatorDev\OpenWeatherMap\Test\Unit\Resource;
44

55
use PHPUnit\Framework\Attributes\DataProvider;
6+
use ProgrammatorDev\OpenWeatherMap\Entity\Stations\MeasurementAggregate;
67
use ProgrammatorDev\OpenWeatherMap\Entity\Stations\Station;
8+
use ProgrammatorDev\OpenWeatherMap\Enum\AggregationInterval;
79
use ProgrammatorDev\OpenWeatherMap\Request\Stations\CloudLayer;
810
use ProgrammatorDev\OpenWeatherMap\Request\Stations\Measurement;
911
use ProgrammatorDev\OpenWeatherMap\Request\Stations\Weather;
@@ -305,6 +307,100 @@ public function testRejectsAnInvalidMeasurementBatchItem(): void
305307
]);
306308
}
307309

310+
public function testAggregatesMeasurements(): void
311+
{
312+
$this->respondWithFixture(
313+
'stations/measurements/aggregate-hour-success.json',
314+
);
315+
316+
$aggregates = $this->api->stations()->measurements(
317+
stationId: ' 6a77ba36adde3b0001343e09 ',
318+
interval: AggregationInterval::HOUR,
319+
startAt: new \DateTimeImmutable('@1786231349'),
320+
endAt: new \DateTimeImmutable('@1786345863'),
321+
limit: 100,
322+
);
323+
$request = $this->client->getLastRequest();
324+
325+
self::assertCount(1, $aggregates);
326+
self::assertContainsOnlyInstancesOf(MeasurementAggregate::class, $aggregates);
327+
self::assertSame(AggregationInterval::HOUR, $aggregates[0]->interval());
328+
self::assertSame(20.5, $aggregates[0]->temperature()?->average());
329+
self::assertSame(0.6, $aggregates[0]->precipitation()?->rain());
330+
self::assertSame('GET', $request->getMethod());
331+
self::assertSame('/data/3.0/measurements', $request->getUri()->getPath());
332+
self::assertSame([
333+
'station_id' => '6a77ba36adde3b0001343e09',
334+
'type' => 'h',
335+
'limit' => '100',
336+
'from' => '1786231349',
337+
'to' => '1786345863',
338+
'appid' => 'api-key',
339+
], $this->query($request));
340+
}
341+
342+
public function testReturnsAnEmptyMeasurementAggregateCollection(): void
343+
{
344+
$this->respondWithFixture(
345+
'stations/measurements/aggregate-minute-empty.json',
346+
);
347+
348+
$aggregates = $this->api->stations()->measurements(
349+
stationId: 'station-id',
350+
interval: AggregationInterval::MINUTE,
351+
startAt: new \DateTimeImmutable('@1786143599'),
352+
endAt: new \DateTimeImmutable('@1786230780'),
353+
limit: 10,
354+
);
355+
356+
self::assertSame([], $aggregates);
357+
}
358+
359+
#[DataProvider('invalidAggregationArguments')]
360+
public function testRejectsInvalidAggregationArguments(
361+
string $stationId,
362+
\DateTimeInterface $startAt,
363+
\DateTimeInterface $endAt,
364+
int $limit,
365+
string $message,
366+
): void {
367+
$this->expectException(\InvalidArgumentException::class);
368+
$this->expectExceptionMessage($message);
369+
370+
$this->api->stations()->measurements(
371+
$stationId,
372+
AggregationInterval::HOUR,
373+
$startAt,
374+
$endAt,
375+
$limit,
376+
);
377+
}
378+
379+
public static function invalidAggregationArguments(): iterable
380+
{
381+
yield 'blank station identifier' => [
382+
' ',
383+
new \DateTimeImmutable('@100'),
384+
new \DateTimeImmutable('@200'),
385+
1,
386+
'The station ID must be a non-empty string.',
387+
];
388+
yield 'reversed date range' => [
389+
'station-id',
390+
new \DateTimeImmutable('@200'),
391+
new \DateTimeImmutable('@100'),
392+
1,
393+
'The end date must be after or equal to the start date.',
394+
];
395+
yield 'non-positive result limit' => [
396+
'station-id',
397+
new \DateTimeImmutable('@100'),
398+
new \DateTimeImmutable('@200'),
399+
0,
400+
'The result limit must be at least 1.',
401+
];
402+
}
403+
308404
#[DataProvider('invalidCreationArguments')]
309405
public function testRejectsInvalidCreationArguments(
310406
string $externalId,

0 commit comments

Comments
 (0)