opsdash-app/opsdash/tests/php/Service/OverviewHistoryServiceTest.php
2026-04-02 18:05:43 +07:00

221 lines
7.9 KiB
PHP

<?php
declare(strict_types=1);
namespace OCA\Opsdash\Tests\Service;
use DateTimeImmutable;
use OCA\Opsdash\Service\CalendarAccessService;
use OCA\Opsdash\Service\CalendarParsingService;
use OCA\Opsdash\Service\OverviewEventsCollector;
use OCA\Opsdash\Service\OverviewHistoryService;
use OCP\Calendar\IManager;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class OverviewHistoryServiceTest extends TestCase {
public function testBuildDayOffTrendUsesCurrentByDayAndPrecomputedHistory(): void {
$calendarService = new class() extends CalendarAccessService {
public function __construct() {}
public function rangeBounds(string $range, int $offset, ?\DateTimeZone $tz = null, ?int $weekStart = null): array {
if ($range === 'week' && $offset === -1) {
return [
new DateTimeImmutable('2024-12-30T00:00:00Z'),
new DateTimeImmutable('2025-01-05T23:59:59Z'),
];
}
return [
new DateTimeImmutable('2025-01-06T00:00:00Z'),
new DateTimeImmutable('2025-01-12T23:59:59Z'),
];
}
};
$manager = $this->createMock(IManager::class);
$logger = $this->createMock(LoggerInterface::class);
$collector = new OverviewEventsCollector($manager, new CalendarParsingService(), $logger);
$service = new OverviewHistoryService($calendarService, $collector);
$currentByDay = [
'2025-01-06' => ['date' => '2025-01-06', 'total_hours' => 1.0],
'2025-01-07' => ['date' => '2025-01-07', 'total_hours' => 2.0],
];
$trend = $service->buildDayOffTrend(
range: 'week',
offset: 0,
currentFrom: new DateTimeImmutable('2025-01-06T00:00:00Z'),
currentTo: new DateTimeImmutable('2025-01-12T23:59:59Z'),
analysisTo: new DateTimeImmutable('2025-01-08T12:00:00Z'),
currentByDay: $currentByDay,
includeAll: false,
selectedIds: ['cal-a'],
calendars: [],
principal: 'principals/users/admin',
userTz: new \DateTimeZone('UTC'),
lookbackWeeks: 1,
precomputedDaysWorked: [1 => 3],
);
$this->assertCount(2, $trend);
$this->assertSame(0, $trend[0]['offset']);
$this->assertSame('This week', $trend[0]['label']);
$this->assertSame(3, $trend[0]['totalDays']);
$this->assertSame(2, $trend[0]['daysWorked']);
$this->assertSame(1, $trend[0]['daysOff']);
$this->assertSame('2025-01-08', $trend[0]['to']);
$this->assertSame(1, $trend[1]['offset']);
$this->assertSame('-1 wk', $trend[1]['label']);
$this->assertSame(7, $trend[1]['totalDays']);
$this->assertSame(3, $trend[1]['daysWorked']);
$this->assertSame(4, $trend[1]['daysOff']);
}
public function testCollectCategoryTotalsForRangeRespectsSelection(): void {
$manager = new class() implements IManager {
public array $responses = [];
public function newQuery(string $principal): object {
return new class() {
public string $calendar = '';
public function addSearchCalendar(string $calendar): void { $this->calendar = $calendar; }
public function setTimerangeStart($from): void {}
public function setTimerangeEnd($to): void {}
};
}
public function searchForPrincipal(object $q): array {
return $this->responses[$q->calendar] ?? [];
}
};
$manager->responses = [
'cal-a' => array_fill(0, 2, ['id' => 1]),
'cal-b' => array_fill(0, 3, ['id' => 2]),
];
$calendarService = new class() extends CalendarAccessService {
public function __construct() {}
};
$calendarParsing = new class() extends CalendarParsingService {
public function parseRows(array $raw, string $calendarName, ?string $calendarId = null): array {
return array_map(
fn ($row) => [
'calendar' => $calendarName,
'calendar_id' => $calendarId,
'hours' => 1.0,
'allday' => false,
'start' => '2025-01-01 00:00:00',
],
$raw,
);
}
};
$logger = $this->createMock(LoggerInterface::class);
$collector = new OverviewEventsCollector($manager, $calendarParsing, $logger);
$service = new OverviewHistoryService($calendarService, $collector);
$calA = new class() {
public function getUri(): string { return 'cal-a'; }
public function getDisplayName(): string { return 'A'; }
};
$calB = new class() {
public function getUri(): string { return 'cal-b'; }
public function getDisplayName(): string { return 'B'; }
};
$categoryMeta = [
'work' => ['id' => 'work', 'label' => 'Work'],
'__uncategorized__' => ['id' => '__uncategorized__', 'label' => 'Unassigned'],
];
$mapCalToCategory = fn (string $calId) => $calId === 'cal-a' ? 'work' : '__uncategorized__';
$res = $service->collectCategoryTotalsForRange(
from: new DateTimeImmutable('2025-01-01T00:00:00Z'),
to: new DateTimeImmutable('2025-01-08T00:00:00Z'),
categoryMeta: $categoryMeta,
calendars: [$calA, $calB],
includeAll: false,
selectedIds: ['cal-a'],
principal: 'principals/users/admin',
mapCalToCategory: $mapCalToCategory,
userTz: new \DateTimeZone('UTC'),
allDayHours: 8.0,
captureDays: true,
);
$this->assertSame(2, $res['events']);
$this->assertSame(2.0, $res['total']);
$this->assertSame(2.0, $res['totals']['work']);
$this->assertSame(0.0, $res['totals']['__uncategorized__']);
$this->assertSame(['2025-01-01'], $res['daysSeen']);
}
public function testCollectCategoryTotalsFallsBackToTimedDurationWhenHoursMissing(): void {
$manager = new class() implements IManager {
public function newQuery(string $principal): object {
return new class() {
public string $calendar = '';
public function addSearchCalendar(string $calendar): void { $this->calendar = $calendar; }
public function setTimerangeStart($from): void {}
public function setTimerangeEnd($to): void {}
};
}
public function searchForPrincipal(object $q): array {
return [['id' => 1]];
}
};
$calendarService = new class() extends CalendarAccessService {
public function __construct() {}
};
$calendarParsing = new class() extends CalendarParsingService {
public function parseRows(array $raw, string $calendarName, ?string $calendarId = null): array {
return [[
'calendar' => $calendarName,
'calendar_id' => $calendarId,
'hours' => 0.0,
'allday' => false,
'start' => '2025-01-01T09:00:00Z',
'end' => '2025-01-01T11:30:00Z',
'startTz' => 'UTC',
'endTz' => 'UTC',
]];
}
};
$logger = $this->createMock(LoggerInterface::class);
$collector = new OverviewEventsCollector($manager, $calendarParsing, $logger);
$service = new OverviewHistoryService($calendarService, $collector);
$calA = new class() {
public function getUri(): string { return 'cal-a'; }
public function getDisplayName(): string { return 'A'; }
};
$categoryMeta = [
'work' => ['id' => 'work', 'label' => 'Work'],
'__uncategorized__' => ['id' => '__uncategorized__', 'label' => 'Unassigned'],
];
$mapCalToCategory = fn (string $calId) => $calId === 'cal-a' ? 'work' : '__uncategorized__';
$res = $service->collectCategoryTotalsForRange(
from: new DateTimeImmutable('2025-01-01T00:00:00Z'),
to: new DateTimeImmutable('2025-01-08T00:00:00Z'),
categoryMeta: $categoryMeta,
calendars: [$calA],
includeAll: true,
selectedIds: [],
principal: 'principals/users/admin',
mapCalToCategory: $mapCalToCategory,
userTz: new \DateTimeZone('UTC'),
allDayHours: 8.0,
captureDays: false,
);
$this->assertSame(1, $res['events']);
$this->assertSame(2.5, $res['total']);
$this->assertSame(2.5, $res['totals']['work']);
$this->assertSame(0.0, $res['totals']['__uncategorized__']);
}
}