refactor(dashboard,history): replace ViewModels with Pinia stores and improve backend

This commit is contained in:
2026-08-05 09:23:18 +02:00
parent a0387126de
commit 90930cd223
29 changed files with 1372 additions and 237 deletions
@@ -0,0 +1,143 @@
<?php
namespace Tests\Unit\Clients;
use App\Clients\ZonneplanPriceClient;
use Carbon\Carbon;
use Illuminate\Http\Client\Factory;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Response;
use Mockery;
use Tests\TestCase;
class ZonneplanPriceClientTest extends TestCase
{
protected function tearDown(): void
{
Mockery::close();
parent::tearDown();
}
public function test_get_electricity_prices_returns_collection(): void
{
$response = Mockery::mock();
$response->shouldReceive('throw')->once();
$response->shouldReceive('json')
->once()
->with('data', [])
->andReturn([
['start_date' => Carbon::now()->format('Y-m-d H:i:s'), 'total_price_tax_included' => 15.0],
]);
$response->shouldReceive('status')->once()->andReturn(200);
$http = Mockery::mock(Factory::class);
$http->shouldReceive('baseUrl')->once()->andReturnSelf();
$http->shouldReceive('withQueryParameters')->once()->andReturnSelf();
$http->shouldReceive('timeout')->once()->andReturnSelf();
$http->shouldReceive('retry')->once()->andReturnSelf();
$http->shouldReceive('get')->once()->andReturn($response);
$client = new ZonneplanPriceClient($http);
$result = $client->getElectricityPrices();
$this->assertCount(1, $result);
}
public function test_get_gas_prices_returns_collection(): void
{
$response = Mockery::mock();
$response->shouldReceive('throw')->once();
$response->shouldReceive('json')
->once()
->with('data', [])
->andReturn([
['start_date' => Carbon::now()->format('Y-m-d H:i:s'), 'total_price_tax_included' => 5.0],
]);
$response->shouldReceive('status')->once()->andReturn(200);
$http = Mockery::mock(Factory::class);
$http->shouldReceive('baseUrl')->once()->andReturnSelf();
$http->shouldReceive('withQueryParameters')->once()->andReturnSelf();
$http->shouldReceive('timeout')->once()->andReturnSelf();
$http->shouldReceive('retry')->once()->andReturnSelf();
$http->shouldReceive('get')->once()->andReturn($response);
$client = new ZonneplanPriceClient($http);
$result = $client->getGasPrices();
$this->assertCount(1, $result);
}
public function test_get_electricity_prices_for_date_returns_collection(): void
{
$date = Carbon::today();
$response = Mockery::mock();
$response->shouldReceive('throw')->once();
$response->shouldReceive('json')
->once()
->with('data', [])
->andReturn([
['start_date' => $date->format('Y-m-d H:i:s'), 'total_price_tax_included' => 15.0],
]);
$response->shouldReceive('status')->once()->andReturn(200);
$http = Mockery::mock(Factory::class);
$http->shouldReceive('baseUrl')->once()->andReturnSelf();
$http->shouldReceive('withQueryParameters')->once()->andReturnSelf();
$http->shouldReceive('timeout')->once()->andReturnSelf();
$http->shouldReceive('retry')->once()->andReturnSelf();
$http->shouldReceive('get')->once()->andReturn($response);
$client = new ZonneplanPriceClient($http);
$result = $client->getElectricityPricesForDate($date);
$this->assertCount(1, $result);
}
public function test_get_gas_prices_for_date_returns_collection(): void
{
$date = Carbon::today();
$response = Mockery::mock();
$response->shouldReceive('throw')->once();
$response->shouldReceive('json')
->once()
->with('data', [])
->andReturn([
['start_date' => $date->format('Y-m-d H:i:s'), 'total_price_tax_included' => 5.0],
]);
$response->shouldReceive('status')->once()->andReturn(200);
$http = Mockery::mock(Factory::class);
$http->shouldReceive('baseUrl')->once()->andReturnSelf();
$http->shouldReceive('withQueryParameters')->once()->andReturnSelf();
$http->shouldReceive('timeout')->once()->andReturnSelf();
$http->shouldReceive('retry')->once()->andReturnSelf();
$http->shouldReceive('get')->once()->andReturn($response);
$client = new ZonneplanPriceClient($http);
$result = $client->getGasPricesForDate($date);
$this->assertCount(1, $result);
}
public function test_client_throws_exception_on_api_failure(): void
{
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Kon energieprijzen niet ophalen');
$response = Mockery::mock();
$response->shouldReceive('throw')->once()->andThrow(
new \Illuminate\Http\Client\ConnectionException('API Error')
);
$http = Mockery::mock(Factory::class);
$http->shouldReceive('baseUrl')->once()->andReturnSelf();
$http->shouldReceive('withQueryParameters')->once()->andReturnSelf();
$http->shouldReceive('timeout')->once()->andReturnSelf();
$http->shouldReceive('retry')->once()->andReturnSelf();
$http->shouldReceive('get')->once()->andReturn($response);
$client = new ZonneplanPriceClient($http);
$client->getElectricityPrices();
}
}
@@ -0,0 +1,189 @@
<?php
namespace Tests\Unit\Repositories;
use App\Contracts\PriceClient;
use App\Data\ElectricityData;
use App\Data\GasData;
use App\Repositories\ZonneplanPriceRepository;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Mockery;
use Tests\TestCase;
class ZonneplanPriceRepositoryTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
config(['cache_prices.ttl' => 60]);
config(['cache_prices.prefix' => 'test:']);
}
protected function tearDown(): void
{
Mockery::close();
Cache::flush();
parent::tearDown();
}
public function test_retrieve_electricity_returns_collection_of_electricity_data(): void
{
$now = Carbon::now();
$electricityResponse = collect([
[
'start_date' => $now->format('Y-m-d H:i:s'),
'end_date' => $now->copy()->addHour()->format('Y-m-d H:i:s'),
'period' => 'elec_hour',
'market_price' => 1500,
'total_price_tax_included' => 15.50,
'price_incl_handling_vat' => 1550,
'price_tax_with_vat' => 300,
'pricing_profile' => 'dynamic',
'carbon_footprint_in_gram' => 500,
'sustainability_score' => 80,
'start_date_datetime' => $now->format('Y-m-d H:i:s'),
],
]);
$client = Mockery::mock(PriceClient::class);
$client->shouldReceive('getElectricityPrices')
->once()
->andReturn($electricityResponse);
$repository = new ZonneplanPriceRepository($client);
$result = $repository->retrieveElectricity();
$this->assertInstanceOf(Collection::class, $result);
$this->assertCount(1, $result);
$this->assertInstanceOf(ElectricityData::class, $result->first());
}
public function test_retrieve_gas_returns_collection_of_gas_data(): void
{
$now = Carbon::now();
$gasResponse = collect([
[
'start_date' => $now->format('Y-m-d H:i:s'),
'end_date' => $now->copy()->addDay()->format('Y-m-d H:i:s'),
'period' => 'gas_day',
'market_price' => 500,
'total_price_tax_included' => 5.50,
'price_incl_handling_vat' => 550,
'price_tax_with_vat' => 100,
'start_date_datetime' => $now->format('Y-m-d H:i:s'),
],
]);
$client = Mockery::mock(PriceClient::class);
$client->shouldReceive('getGasPrices')
->once()
->andReturn($gasResponse);
$repository = new ZonneplanPriceRepository($client);
$result = $repository->retrieveGas();
$this->assertInstanceOf(Collection::class, $result);
$this->assertCount(1, $result);
$this->assertInstanceOf(GasData::class, $result->first());
}
public function test_retrieve_electricity_for_date_returns_collection(): void
{
$date = Carbon::today();
$electricityResponse = collect([
[
'start_date' => $date->copy()->setHour(0)->format('Y-m-d H:i:s'),
'end_date' => $date->copy()->setHour(1)->format('Y-m-d H:i:s'),
'period' => 'elec_hour',
'market_price' => 1500,
'total_price_tax_included' => 15.50,
'price_incl_handling_vat' => 1550,
'price_tax_with_vat' => 300,
'pricing_profile' => 'dynamic',
'carbon_footprint_in_gram' => 500,
'sustainability_score' => 80,
'start_date_datetime' => $date->copy()->setHour(0)->format('Y-m-d H:i:s'),
],
]);
$client = Mockery::mock(PriceClient::class);
$client->shouldReceive('getElectricityPricesForDate')
->once()
->with($date)
->andReturn($electricityResponse);
$repository = new ZonneplanPriceRepository($client);
$result = $repository->retrieveElectricityForDate($date);
$this->assertInstanceOf(Collection::class, $result);
$this->assertCount(1, $result);
$this->assertInstanceOf(ElectricityData::class, $result->first());
}
public function test_retrieve_gas_for_date_returns_collection(): void
{
$date = Carbon::today();
$gasResponse = collect([
[
'start_date' => $date->format('Y-m-d H:i:s'),
'end_date' => $date->copy()->addDay()->format('Y-m-d H:i:s'),
'period' => 'gas_day',
'market_price' => 500,
'total_price_tax_included' => 5.50,
'price_incl_handling_vat' => 550,
'price_tax_with_vat' => 100,
'start_date_datetime' => $date->format('Y-m-d H:i:s'),
],
]);
$client = Mockery::mock(PriceClient::class);
$client->shouldReceive('getGasPricesForDate')
->once()
->with($date)
->andReturn($gasResponse);
$repository = new ZonneplanPriceRepository($client);
$result = $repository->retrieveGasForDate($date);
$this->assertInstanceOf(Collection::class, $result);
$this->assertCount(1, $result);
$this->assertInstanceOf(GasData::class, $result->first());
}
public function test_retrieve_electricity_caches_result(): void
{
$now = Carbon::now();
$electricityResponse = collect([
[
'start_date' => $now->format('Y-m-d H:i:s'),
'end_date' => $now->copy()->addHour()->format('Y-m-d H:i:s'),
'period' => 'elec_hour',
'market_price' => 1500,
'total_price_tax_included' => 15.50,
'price_incl_handling_vat' => 1550,
'price_tax_with_vat' => 300,
'pricing_profile' => 'dynamic',
'carbon_footprint_in_gram' => 500,
'sustainability_score' => 80,
'start_date_datetime' => $now->format('Y-m-d H:i:s'),
],
]);
$client = Mockery::mock(PriceClient::class);
$client->shouldReceive('getElectricityPrices')
->once()
->andReturn($electricityResponse);
$repository = new ZonneplanPriceRepository($client);
$result1 = $repository->retrieveElectricity();
$result2 = $repository->retrieveElectricity();
$this->assertEquals(
$result1->first()->totalPriceTaxIncluded,
$result2->first()->totalPriceTaxIncluded
);
}
}
@@ -0,0 +1,129 @@
<?php
namespace Tests\Unit\Services;
use App\Contracts\PriceRepository;
use App\Data\ElectricityData;
use App\Data\GasData;
use App\Features\Dashboard\Services\DashboardService;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Mockery;
use Tests\TestCase;
class DashboardServiceTest extends TestCase
{
protected function tearDown(): void
{
Mockery::close();
parent::tearDown();
}
public function test_get_dashboard_data_returns_current_cheapest_sustainable_and_expensive_prices(): void
{
$now = Carbon::now();
$electricityData = collect([
$this->createElectricityData($now->copy()->subMinutes(30), 15.0, 80),
$this->createElectricityData($now->copy()->addHour(), 12.0, 90),
$this->createElectricityData($now->copy()->addHours(2), 20.0, 60),
$this->createElectricityData($now->copy()->addHours(3), 10.0, 95),
]);
$gasData = collect([
$this->createGasData($now->copy()->startOfDay(), 5.0),
$this->createGasData($now->copy()->addDay(), 4.0),
$this->createGasData($now->copy()->addDays(2), 6.0),
]);
$repository = Mockery::mock(PriceRepository::class);
$repository->shouldReceive('retrieveElectricity')->once()->andReturn($electricityData);
$repository->shouldReceive('retrieveGas')->once()->andReturn($gasData);
$service = new DashboardService($repository);
$result = $service->getDashboardData();
$this->assertEquals(15.0, $result->electricity->totalPriceTaxIncluded);
$this->assertEquals(10.0, $result->electricityCheapest->totalPriceTaxIncluded);
$this->assertEquals(95, $result->electricityDurable->sustainabilityScore);
$this->assertEquals(20.0, $result->electricityPriciest->totalPriceTaxIncluded);
$this->assertEquals(5.0, $result->gas->totalPriceTaxIncluded);
$this->assertEquals(4.0, $result->gasCheapest->totalPriceTaxIncluded);
$this->assertEquals(6.0, $result->gasPriciest->totalPriceTaxIncluded);
}
public function test_get_dashboard_data_returns_null_for_missing_current_price(): void
{
$now = Carbon::now();
$electricityData = collect([
$this->createElectricityData($now->copy()->addHours(5), 15.0, 80),
]);
$gasData = collect([
$this->createGasData($now->copy()->addDays(5), 5.0),
]);
$repository = Mockery::mock(PriceRepository::class);
$repository->shouldReceive('retrieveElectricity')->once()->andReturn($electricityData);
$repository->shouldReceive('retrieveGas')->once()->andReturn($gasData);
$service = new DashboardService($repository);
$result = $service->getDashboardData();
$this->assertNull($result->electricity);
$this->assertNull($result->gas);
$this->assertNotNull($result->electricityCheapest);
$this->assertNotNull($result->gasCheapest);
}
public function test_get_dashboard_data_with_empty_collections(): void
{
$repository = Mockery::mock(PriceRepository::class);
$repository->shouldReceive('retrieveElectricity')->once()->andReturn(collect());
$repository->shouldReceive('retrieveGas')->once()->andReturn(collect());
$service = new DashboardService($repository);
$result = $service->getDashboardData();
$this->assertNull($result->electricity);
$this->assertNull($result->electricityCheapest);
$this->assertNull($result->electricityDurable);
$this->assertNull($result->electricityPriciest);
$this->assertNull($result->gas);
$this->assertNull($result->gasCheapest);
$this->assertNull($result->gasPriciest);
}
private function createElectricityData(Carbon $startDate, float $price, int $sustainabilityScore): ElectricityData
{
return new ElectricityData(
startDate: $startDate,
endDate: $startDate->copy()->addHour(),
period: 'elec_hour',
marketPrice: (int) ($price * 100),
totalPriceTaxIncluded: $price,
priceInclHandlingVat: (int) ($price * 100),
priceTaxWithVat: (int) ($price * 20),
pricingProfile: 'dynamic',
carbonFootprintInGram: 500,
sustainabilityScore: $sustainabilityScore,
startDateDatetime: $startDate,
);
}
private function createGasData(Carbon $startDate, float $price): GasData
{
return new GasData(
startDate: $startDate,
endDate: $startDate->copy()->addDay(),
period: 'gas_day',
marketPrice: (int) ($price * 100),
totalPriceTaxIncluded: $price,
priceInclHandlingVat: (int) ($price * 100),
priceTaxWithVat: (int) ($price * 20),
startDateDatetime: $startDate,
);
}
}
+103
View File
@@ -0,0 +1,103 @@
<?php
namespace Tests\Unit\Services;
use App\Contracts\PriceRepository;
use App\Data\ElectricityData;
use App\Data\GasData;
use App\Features\History\Services\HistoryService;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Mockery;
use Tests\TestCase;
class HistoryServiceTest extends TestCase
{
protected function tearDown(): void
{
Mockery::close();
parent::tearDown();
}
public function test_get_history_data_returns_electricity_and_gas_for_date(): void
{
$date = Carbon::today();
$electricityData = collect([
$this->createElectricityData($date->copy()->setHour(0), 15.0),
$this->createElectricityData($date->copy()->setHour(1), 16.0),
]);
$gasData = collect([
$this->createGasData($date, 5.0),
]);
$repository = Mockery::mock(PriceRepository::class);
$repository->shouldReceive('retrieveElectricityForDate')
->once()
->with($date)
->andReturn($electricityData);
$repository->shouldReceive('retrieveGasForDate')
->once()
->with($date)
->andReturn($gasData);
$service = new HistoryService($repository);
$result = $service->getHistoryData($date);
$this->assertCount(2, $result->electricity);
$this->assertCount(1, $result->gas);
$this->assertEquals(15.0, $result->electricity->first()->totalPriceTaxIncluded);
$this->assertEquals(5.0, $result->gas->first()->totalPriceTaxIncluded);
}
public function test_get_history_data_with_empty_collections(): void
{
$date = Carbon::today();
$repository = Mockery::mock(PriceRepository::class);
$repository->shouldReceive('retrieveElectricityForDate')
->once()
->andReturn(collect());
$repository->shouldReceive('retrieveGasForDate')
->once()
->andReturn(collect());
$service = new HistoryService($repository);
$result = $service->getHistoryData($date);
$this->assertCount(0, $result->electricity);
$this->assertCount(0, $result->gas);
}
private function createElectricityData(Carbon $startDate, float $price): ElectricityData
{
return new ElectricityData(
startDate: $startDate,
endDate: $startDate->copy()->addHour(),
period: 'elec_hour',
marketPrice: (int) ($price * 100),
totalPriceTaxIncluded: $price,
priceInclHandlingVat: (int) ($price * 100),
priceTaxWithVat: (int) ($price * 20),
pricingProfile: 'dynamic',
carbonFootprintInGram: 500,
sustainabilityScore: 80,
startDateDatetime: $startDate,
);
}
private function createGasData(Carbon $startDate, float $price): GasData
{
return new GasData(
startDate: $startDate,
endDate: $startDate->copy()->addDay(),
period: 'gas_day',
marketPrice: (int) ($price * 100),
totalPriceTaxIncluded: $price,
priceInclHandlingVat: (int) ($price * 100),
priceTaxWithVat: (int) ($price * 20),
startDateDatetime: $startDate,
);
}
}