kimai-plugin-heatmap/Tests/Service/HeatmapServiceTest.php

273 lines
9.1 KiB
PHP

<?php
namespace KimaiPlugin\KimaiHeatmapBundle\Tests\Service;
use App\Entity\User;
use App\Repository\TimesheetRepository;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
use KimaiPlugin\KimaiHeatmapBundle\Service\HeatmapService;
use PHPUnit\Framework\TestCase;
class HeatmapServiceTest extends TestCase
{
public function testGetDailyAggregationReturnsFormattedResults(): void
{
$mockResults = [
['day' => '2026-04-01', 'duration' => 7200, 'count' => 3],
['day' => '2026-04-02', 'duration' => 3600, 'count' => 1],
];
$service = $this->createServiceWithResults($mockResults);
$result = $service->getDailyAggregation(
$this->createMock(User::class),
new \DateTimeImmutable('2026-04-01'),
new \DateTimeImmutable('2026-04-30')
);
$this->assertCount(2, $result);
$this->assertEquals('2026-04-01', $result[0]['date']);
$this->assertEquals(2.0, $result[0]['hours']);
$this->assertEquals(3, $result[0]['count']);
$this->assertEquals('2026-04-02', $result[1]['date']);
$this->assertEquals(1.0, $result[1]['hours']);
$this->assertEquals(1, $result[1]['count']);
}
public function testGetDailyAggregationReturnsEmptyForNoData(): void
{
$service = $this->createServiceWithResults([]);
$result = $service->getDailyAggregation(
$this->createMock(User::class),
new \DateTimeImmutable('2026-04-01'),
new \DateTimeImmutable('2026-04-30')
);
$this->assertCount(0, $result);
}
public function testHoursRoundedToTwoDecimals(): void
{
$mockResults = [
['day' => '2026-04-01', 'duration' => 5431, 'count' => 2],
];
$service = $this->createServiceWithResults($mockResults);
$result = $service->getDailyAggregation(
$this->createMock(User::class),
new \DateTimeImmutable('2026-04-01'),
new \DateTimeImmutable('2026-04-30')
);
$this->assertEquals(1.51, $result[0]['hours']);
}
public function testGetHourlyAggregationReturnsFormattedResults(): void
{
$service = $this->createServiceWithNativeResults([
['hour_slot' => 9, 'duration' => 7200, 'count' => 5],
]);
$user = $this->createMock(User::class);
$user->method('getTimezone')->willReturn('Europe/Berlin');
$user->method('getId')->willReturn(1);
$result = $service->getHourlyAggregation(
$user,
new \DateTimeImmutable('2026-04-01'),
new \DateTimeImmutable('2026-04-30')
);
$this->assertCount(1, $result);
$this->assertSame(9, $result[0]['hour']);
$this->assertSame(2.0, $result[0]['hours']);
$this->assertSame(5, $result[0]['count']);
}
public function testGetHourlyAggregationReturnsEmptyForNoData(): void
{
$service = $this->createServiceWithNativeResults([]);
$user = $this->createMock(User::class);
$user->method('getTimezone')->willReturn('UTC');
$user->method('getId')->willReturn(1);
$result = $service->getHourlyAggregation(
$user,
new \DateTimeImmutable('2026-04-01'),
new \DateTimeImmutable('2026-04-30')
);
$this->assertCount(0, $result);
}
public function testGetDayHourAggregationReturnsFormattedResults(): void
{
// MySQL DAYOFWEEK: 2 = Monday
// weekStart=monday -> Monday = day 0
$service = $this->createServiceWithNativeResults([
['dow' => 2, 'hour_slot' => 14, 'duration' => 3600, 'count' => 1],
]);
$user = $this->createMock(User::class);
$user->method('getTimezone')->willReturn('Europe/Berlin');
$user->method('getId')->willReturn(1);
$result = $service->getDayHourAggregation(
$user,
new \DateTimeImmutable('2026-04-01'),
new \DateTimeImmutable('2026-04-30'),
null, null, null,
'monday'
);
$this->assertCount(1, $result);
$this->assertSame(0, $result[0]['day']);
$this->assertSame(14, $result[0]['hour']);
$this->assertSame(1.0, $result[0]['hours']);
$this->assertSame(1, $result[0]['count']);
}
public function testGetDayHourAggregationSundayStart(): void
{
// MySQL DAYOFWEEK: 2 = Monday
// weekStart=sunday -> Monday = day 1
$service = $this->createServiceWithNativeResults([
['dow' => 2, 'hour_slot' => 14, 'duration' => 3600, 'count' => 1],
]);
$user = $this->createMock(User::class);
$user->method('getTimezone')->willReturn('Europe/Berlin');
$user->method('getId')->willReturn(1);
$result = $service->getDayHourAggregation(
$user,
new \DateTimeImmutable('2026-04-01'),
new \DateTimeImmutable('2026-04-30'),
null, null, null,
'sunday'
);
$this->assertCount(1, $result);
$this->assertSame(1, $result[0]['day']);
$this->assertSame(14, $result[0]['hour']);
$this->assertSame(1.0, $result[0]['hours']);
$this->assertSame(1, $result[0]['count']);
}
public function testGetDailyAggregationWithActivityFilter(): void
{
$service = $this->createServiceWithResults([
['day' => '2026-04-01', 'duration' => 3600, 'count' => 1],
]);
$result = $service->getDailyAggregation(
$this->createMock(User::class),
new \DateTimeImmutable('2026-04-01'),
new \DateTimeImmutable('2026-04-30'),
null,
null,
42
);
$this->assertCount(1, $result);
}
public function testGetDailyAggregationWithCustomerFilter(): void
{
$service = $this->createServiceWithResults([
['day' => '2026-04-01', 'duration' => 3600, 'count' => 1],
]);
$result = $service->getDailyAggregation(
$this->createMock(User::class),
new \DateTimeImmutable('2026-04-01'),
new \DateTimeImmutable('2026-04-30'),
null,
99
);
$this->assertCount(1, $result);
}
public function testGetUserCustomersReturnsFormattedResults(): void
{
$service = $this->createServiceWithResults([
['customerId' => 1, 'name' => 'Acme'],
]);
$result = $service->getUserCustomers($this->createMock(User::class));
$this->assertCount(1, $result);
$this->assertSame(1, $result[0]['id']);
$this->assertSame('Acme', $result[0]['name']);
}
public function testGetUserActivitiesReturnsFormattedResults(): void
{
$service = $this->createServiceWithResults([
['activityId' => 5, 'name' => 'Dev'],
]);
$result = $service->getUserActivities($this->createMock(User::class));
$this->assertCount(1, $result);
$this->assertSame(5, $result[0]['id']);
$this->assertSame('Dev', $result[0]['name']);
}
public function testGetUserActivitiesWithProjectScope(): void
{
$service = $this->createServiceWithResults([
['activityId' => 5, 'name' => 'Dev'],
]);
$result = $service->getUserActivities($this->createMock(User::class), 10);
$this->assertCount(1, $result);
$this->assertSame(5, $result[0]['id']);
}
private function createServiceWithResults(array $results): HeatmapService
{
$query = $this->createMock(AbstractQuery::class);
$query->method('getResult')->willReturn($results);
$qb = $this->createMock(QueryBuilder::class);
$qb->method('select')->willReturnSelf();
$qb->method('addSelect')->willReturnSelf();
$qb->method('andWhere')->willReturnSelf();
$qb->method('setParameter')->willReturnSelf();
$qb->method('groupBy')->willReturnSelf();
$qb->method('orderBy')->willReturnSelf();
$qb->method('join')->willReturnSelf();
$qb->method('expr')->willReturn(new Expr());
$qb->method('getQuery')->willReturn($query);
$repo = $this->createMock(TimesheetRepository::class);
$repo->method('createQueryBuilder')->willReturn($qb);
return new HeatmapService($repo);
}
private function createServiceWithNativeResults(array $results): HeatmapService
{
$statement = $this->createMock(\Doctrine\DBAL\Result::class);
$statement->method('fetchAllAssociative')->willReturn($results);
$connection = $this->createMock(\Doctrine\DBAL\Connection::class);
$connection->method('executeQuery')->willReturn($statement);
$em = $this->createMock(\Doctrine\ORM\EntityManagerInterface::class);
$em->method('getConnection')->willReturn($connection);
$repo = $this->getMockBuilder(TimesheetRepository::class)
->disableOriginalConstructor()
->onlyMethods(['getEntityManager'])
->getMock();
$repo->method('getEntityManager')->willReturn($em);
return new HeatmapService($repo);
}
}