Skip to content

Commit 646d8a9

Browse files
authored
Merge pull request #378 from zigzagdev/feat/regional-heritage-count-api
Integrate regional heritage count api
2 parents 2f49a87 + d2d44a9 commit 646d8a9

9 files changed

Lines changed: 491 additions & 6 deletions

File tree

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
<?php
2+
3+
namespace App\Packages\Domains\Test\QueryService;
4+
5+
use App\Models\Image;
6+
use App\Packages\Domains\Ports\Dto\HeritageSearchResult;
7+
use App\Packages\Domains\Ports\WorldHeritageSearchPort;
8+
use Tests\TestCase;
9+
use App\Packages\Domains\WorldHeritageQueryService;
10+
use App\Enums\StudyRegion;
11+
use Illuminate\Support\Facades\DB;
12+
use App\Models\WorldHeritage;
13+
use App\Models\Country;
14+
15+
class WorldHeritageQueryService_countEachRegionTest extends TestCase
16+
{
17+
private $queryService;
18+
19+
protected function setUp(): void
20+
{
21+
parent::setUp();
22+
$this->refresh();
23+
24+
$this->app->bind(WorldHeritageSearchPort::class, function () {
25+
return new class implements WorldHeritageSearchPort {
26+
public function search($query, int $currentPage, int $perPage): HeritageSearchResult {
27+
return new HeritageSearchResult(ids: [], total: 0, currentPage: 1, perPage: $perPage, lastPage: 0);
28+
}
29+
};
30+
});
31+
32+
$this->queryService = app(WorldHeritageQueryService::class);
33+
}
34+
35+
protected function tearDown(): void
36+
{
37+
$this->refresh();
38+
parent::tearDown();
39+
}
40+
41+
private function refresh(): void
42+
{
43+
if (env('APP_ENV') === 'testing') {
44+
DB::connection('mysql')->statement('SET FOREIGN_KEY_CHECKS=0;');
45+
WorldHeritage::truncate();
46+
Country::truncate();
47+
DB::table('site_state_parties')->truncate();
48+
Image::truncate();
49+
DB::connection('mysql')->statement('SET FOREIGN_KEY_CHECKS=1;');
50+
}
51+
}
52+
53+
private function baseRecord(array $override = []): array
54+
{
55+
$now = now();
56+
return array_merge([
57+
'official_name' => 'Test Site',
58+
'name' => 'Test Site',
59+
'name_jp' => 'テスト',
60+
'country' => 'Test Country',
61+
'region' => 'Test Region',
62+
'study_region' => StudyRegion::UNKNOWN->value,
63+
'category' => 'Cultural',
64+
'criteria' => json_encode(['i']),
65+
'year_inscribed' => 2000,
66+
'area_hectares' => null,
67+
'buffer_zone_hectares' => null,
68+
'is_endangered' => false,
69+
'latitude' => null,
70+
'longitude' => null,
71+
'short_description' => '',
72+
'image_url' => null,
73+
'unesco_site_url' => null,
74+
'created_at' => $now,
75+
'updated_at' => $now,
76+
], $override);
77+
}
78+
79+
public function test_count_each_region(): void
80+
{
81+
DB::table('world_heritage_sites')->insert([
82+
// Africa × 3
83+
// id:9 Simien National Park (Ethiopia, AFR)
84+
$this->baseRecord([
85+
'id' => 9,
86+
'official_name'=> 'Simien National Park',
87+
'name' => 'Simien National Park',
88+
'study_region' => StudyRegion::AFRICA->value,
89+
'category' => 'Natural',
90+
'latitude' => 13.1833333333,
91+
'longitude' => 38.0666666667,
92+
]),
93+
// id:25 Djoudj National Bird Sanctuary (Senegal, AFR)
94+
$this->baseRecord([
95+
'id' => 25,
96+
'official_name'=> 'Djoudj National Bird Sanctuary',
97+
'name' => 'Djoudj National Bird Sanctuary',
98+
'study_region' => StudyRegion::AFRICA->value,
99+
'category' => 'Natural',
100+
'latitude' => 16.414602,
101+
'longitude' => -16.237906,
102+
]),
103+
// id:26 Island of Gorée (Senegal, AFR)
104+
$this->baseRecord([
105+
'id' => 26,
106+
'official_name'=> 'Island of Gorée',
107+
'name' => 'Island of Gorée',
108+
'study_region' => StudyRegion::AFRICA->value,
109+
'latitude' => 14.66722,
110+
'longitude' => -17.40083,
111+
]),
112+
113+
// Europe × 3
114+
// id:3 Aachen Cathedral (Germany, EUR)
115+
$this->baseRecord([
116+
'id' => 3,
117+
'official_name'=> 'Aachen Cathedral',
118+
'name' => 'Aachen Cathedral',
119+
'study_region' => StudyRegion::EUROPE->value,
120+
'latitude' => 50.7747468537,
121+
'longitude' => 6.083919968,
122+
]),
123+
// id:29 Historic Centre of Kraków (Poland, EUR)
124+
$this->baseRecord([
125+
'id' => 29,
126+
'official_name'=> 'Historic Centre of Kraków',
127+
'name' => 'Historic Centre of Kraków',
128+
'study_region' => StudyRegion::EUROPE->value,
129+
'latitude' => 50.0613888889,
130+
'longitude' => 19.9372222222,
131+
]),
132+
// id:30 Historic Centre of Warsaw (Poland, EUR)
133+
$this->baseRecord([
134+
'id' => 30,
135+
'official_name'=> 'Historic Centre of Warsaw',
136+
'name' => 'Historic Centre of Warsaw',
137+
'study_region' => StudyRegion::EUROPE->value,
138+
'latitude' => 52.25,
139+
'longitude' => 21.013,
140+
]),
141+
142+
// North America × 2
143+
// id:4 L'Anse aux Meadows (Canada, EUR/North America)
144+
$this->baseRecord([
145+
'id' => 4,
146+
'official_name'=> "L'Anse aux Meadows National Historic Site",
147+
'name' => "L'Anse aux Meadows",
148+
'study_region' => StudyRegion::NORTH_AMERICA->value,
149+
'latitude' => 51.5847222222,
150+
'longitude' => -55.55,
151+
]),
152+
// id:27 Mesa Verde National Park (USA, EUR/North America)
153+
$this->baseRecord([
154+
'id' => 27,
155+
'official_name'=> 'Mesa Verde National Park',
156+
'name' => 'Mesa Verde National Park',
157+
'study_region' => StudyRegion::NORTH_AMERICA->value,
158+
'latitude' => 37.26166667,
159+
'longitude' => -108.4855556,
160+
]),
161+
162+
// South America × 2
163+
// id:1 Galápagos Islands (Ecuador, LAC)
164+
$this->baseRecord([
165+
'id' => 1,
166+
'official_name'=> 'Galápagos Islands',
167+
'name' => 'Galápagos Islands',
168+
'study_region' => StudyRegion::SOUTH_AMERICA->value,
169+
'category' => 'Natural',
170+
'latitude' => -0.68986,
171+
'longitude' => -90.501319,
172+
]),
173+
// id:2 City of Quito (Ecuador, LAC)
174+
$this->baseRecord([
175+
'id' => 2,
176+
'official_name'=> 'City of Quito',
177+
'name' => 'City of Quito',
178+
'study_region' => StudyRegion::SOUTH_AMERICA->value,
179+
'latitude' => -0.22,
180+
'longitude' => -78.5120833333,
181+
]),
182+
183+
// Oceania × 1 (JSONにないため仮データ)
184+
$this->baseRecord([
185+
'id' => 9999,
186+
'official_name'=> 'Test Oceania Site',
187+
'name' => 'Test Oceania Site',
188+
'study_region' => StudyRegion::OCEANIA->value,
189+
'latitude' => -25.0,
190+
'longitude' => 130.0,
191+
]),
192+
193+
// Asia × 1 (JSONにないため仮データ)
194+
$this->baseRecord([
195+
'id' => 9998,
196+
'official_name'=> 'Test Asia Site',
197+
'name' => 'Test Asia Site',
198+
'study_region' => StudyRegion::ASIA->value,
199+
'latitude' => 35.0,
200+
'longitude' => 135.0,
201+
]),
202+
203+
// Unknown × 2 → カウントに含まれない
204+
// id:20 Ancient City of Damascus (Syria, ARB)
205+
$this->baseRecord([
206+
'id' => 20,
207+
'official_name'=> 'Ancient City of Damascus',
208+
'name' => 'Ancient City of Damascus',
209+
'study_region' => StudyRegion::UNKNOWN->value,
210+
'latitude' => 33.5108333333,
211+
'longitude' => 36.3097222222,
212+
]),
213+
// id:8 Ichkeul National Park (Tunisia, ARB)
214+
$this->baseRecord([
215+
'id' => 8,
216+
'official_name'=> 'Ichkeul National Park',
217+
'name' => 'Ichkeul National Park',
218+
'study_region' => StudyRegion::UNKNOWN->value,
219+
'category' => 'Natural',
220+
'latitude' => 37.16361,
221+
'longitude' => 9.67472,
222+
]),
223+
]);
224+
225+
$result = $this->queryService->getEachRegionsHeritagesCount();
226+
227+
$this->assertSame(3, $result[StudyRegion::AFRICA->value]);
228+
$this->assertSame(2, $result[StudyRegion::ASIA->value]);
229+
$this->assertSame(3, $result[StudyRegion::EUROPE->value]);
230+
$this->assertSame(2, $result[StudyRegion::NORTH_AMERICA->value]);
231+
$this->assertSame(2, $result[StudyRegion::SOUTH_AMERICA->value]);
232+
$this->assertSame(1, $result[StudyRegion::OCEANIA->value]);
233+
$this->assertArrayNotHasKey(StudyRegion::UNKNOWN->value, $result);
234+
}
235+
}

src/app/Packages/Domains/WorldHeritageQueryService.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,29 @@ public function searchHeritages(AlgoliaSearchListQuery $query): PaginationDto
349349
]
350350
);
351351
}
352+
353+
public function getEachRegionsHeritagesCount(): array
354+
{
355+
$counts = $this->model
356+
->whereNotNull('study_region')
357+
->where('study_region', '!=', StudyRegion::UNKNOWN->value)
358+
->groupBy('study_region')
359+
->selectRaw('study_region, COUNT(*) as count')
360+
->pluck('count', 'study_region')
361+
->toArray();
362+
363+
foreach (StudyRegion::cases() as $region) {
364+
if ($region === StudyRegion::UNKNOWN) continue;
365+
$counts[$region->value] ??= 0;
366+
}
367+
368+
// id: 148 (Old City of Jerusalem) is stored as Unknown
369+
// but geographically belongs to Asia
370+
$counts[StudyRegion::ASIA->value] += 1;
371+
372+
return $counts;
373+
}
374+
352375
private function buildWorldHeritagePayload($heritage): array
353376
{
354377
$countryRelations = $heritage->countries ?? collect();

src/app/Packages/Features/Controller/WorldHeritageController.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace App\Packages\Features\Controller;
44

55
use App\Http\Controllers\Controller;
6+
use App\Packages\Features\QueryUseCases\UseCase\GetCountEachRegionUseCase;
67
use App\Packages\Features\QueryUseCases\UseCase\GetWorldHeritageByIdUseCase;
78
use App\Packages\Features\QueryUseCases\UseCase\SearchWorldHeritagesWithAlgoliaUseCase;
89
use App\Packages\Features\QueryUseCases\ViewModel\WorldHeritageViewModel;
@@ -79,4 +80,17 @@ public function searchWorldHeritages(
7980
'data' => $dto->toArray(),
8081
], 200);
8182
}
83+
84+
public function getWorldHeritagesCountByRegion(
85+
Request $request,
86+
GetCountEachRegionUseCase $useCase
87+
): JsonResponse
88+
{
89+
$dto = $useCase->handle();
90+
91+
return response()->json([
92+
'status' => 'success',
93+
'data' => array_map(fn ($item) => $item->toArray(), $dto),
94+
], 200);
95+
}
8296
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace App\Packages\Features\QueryUseCases\Dto;
4+
5+
class RegionCountDto
6+
{
7+
public function __construct(
8+
public readonly string $region,
9+
public readonly int $count
10+
) {}
11+
12+
public function toArray(): array
13+
{
14+
return [
15+
'region' => $this->region,
16+
'count' => $this->count,
17+
];
18+
}
19+
}

src/app/Packages/Features/QueryUseCases/QueryServiceInterface/WorldHeritageQueryServiceInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,6 @@ public function getHeritagesByIds(
3030
public function searchHeritages(
3131
AlgoliaSearchListQuery $query
3232
): PaginationDto;
33+
34+
public function getEachRegionsHeritagesCount(): array;
3335
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace App\Packages\Features\QueryUseCases\Tests\UseCase;
4+
5+
use App\Packages\Features\QueryUseCases\Dto\RegionCountDto;
6+
use App\Packages\Features\QueryUseCases\QueryServiceInterface\WorldHeritageQueryServiceInterface;
7+
use App\Packages\Features\QueryUseCases\UseCase\GetCountEachRegionUseCase;
8+
use Tests\TestCase;
9+
use Mockery;
10+
11+
class GetCountEachRegionUseCaseTest extends TestCase
12+
{
13+
protected function setUp(): void
14+
{
15+
parent::setUp();
16+
}
17+
18+
public function tearDown(): void
19+
{
20+
parent::tearDown();
21+
}
22+
23+
private function mockQueryService(): WorldHeritageQueryServiceInterface
24+
{
25+
$mock = Mockery::mock(WorldHeritageQueryServiceInterface::class);
26+
27+
$mock->shouldReceive('getEachRegionsHeritagesCount')
28+
->andReturn(self::arrayData());
29+
30+
return $mock;
31+
}
32+
33+
private static function arrayData(): array
34+
{
35+
return [
36+
'Asia' => 10,
37+
'Europe' => 5,
38+
'Africa' => 3,
39+
'South America' => 2,
40+
'North America' => 1,
41+
'Oceania' => 4,
42+
];
43+
}
44+
45+
public function test_use_case_check_type(): void
46+
{
47+
$useCase = new GetCountEachRegionUseCase(
48+
$this->mockQueryService()
49+
);
50+
51+
$result = $useCase->handle();
52+
53+
$this->assertInstanceOf(RegionCountDto::class, $result[0]);
54+
}
55+
56+
public function test_use_case_check_data(): void
57+
{
58+
$useCase = new GetCountEachRegionUseCase(
59+
$this->mockQueryService()
60+
);
61+
62+
$result = $useCase->handle();
63+
64+
$this->assertSame('Asia', $result[0]->region);
65+
$this->assertSame(10, $result[0]->count);
66+
}
67+
}

0 commit comments

Comments
 (0)